blob: a7e81278838ae65a14aca77002d66a2fba1b8208 [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(Delegate * aDelegate, EndpointId aEndpointId, BitMask<Feature> aFeature) :
AttributeAccessInterface(MakeOptional(aEndpointId), Id), CommandHandlerInterface(MakeOptional(aEndpointId), Id),
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);
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 (mDelegate->GetNumberOfSupportedAreas() == 0)
{
return aEncoder.EncodeEmptyList();
}
return aEncoder.EncodeList([this](const auto & encoder) -> CHIP_ERROR {
uint8_t locationIndex = 0;
AreaStructureWrapper supportedArea;
while (mDelegate->GetSupportedAreaByIndex(locationIndex++, supportedArea))
{
ReturnErrorOnFailure(encoder.Encode(supportedArea));
}
return CHIP_NO_ERROR;
});
}
CHIP_ERROR Instance::ReadSupportedMaps(AttributeValueEncoder & aEncoder)
{
if (mDelegate->GetNumberOfSupportedMaps() == 0)
{
return aEncoder.EncodeEmptyList();
}
return aEncoder.EncodeList([this](const auto & encoder) -> CHIP_ERROR {
uint32_t mapIndex = 0;
MapStructureWrapper supportedMap;
while (mDelegate->GetSupportedMapByIndex(mapIndex++, supportedMap))
{
ReturnErrorOnFailure(encoder.Encode(supportedMap));
}
return CHIP_NO_ERROR;
});
}
CHIP_ERROR Instance::ReadSelectedAreas(AttributeValueEncoder & aEncoder)
{
if (mDelegate->GetNumberOfSelectedAreas() == 0)
{
return aEncoder.EncodeEmptyList();
}
return aEncoder.EncodeList([this](const auto & encoder) -> CHIP_ERROR {
uint32_t locationIndex = 0;
uint32_t selectedArea;
while (mDelegate->GetSelectedAreaByIndex(locationIndex++, selectedArea))
{
ReturnErrorOnFailure(encoder.Encode(selectedArea));
}
return CHIP_NO_ERROR;
});
}
CHIP_ERROR Instance::ReadProgress(AttributeValueEncoder & aEncoder)
{
if (mDelegate->GetNumberOfProgressElements() == 0)
{
return aEncoder.EncodeEmptyList();
}
return aEncoder.EncodeList([this](const auto & encoder) -> CHIP_ERROR {
uint32_t locationIndex = 0;
Structs::ProgressStruct::Type progressElement;
while (mDelegate->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;
}
}
// if number of selected locations in parameter matches number in attribute - the locations *might* be the same
bool matchesCurrentSelectedAreas = (numberOfAreas == mDelegate->GetNumberOfSelectedAreas());
if (numberOfAreas != 0)
{
// do as much parameter validation as we can
{
uint32_t ignoredIndex = 0;
uint32_t oldSelectedArea;
uint32_t i = 0;
auto iAreaIter = req.newAreas.begin();
while (iAreaIter.Next())
{
uint32_t aSelectedArea = iAreaIter.GetValue();
// 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 (!IsSupportedArea(aSelectedArea))
{
exitResponse(SelectAreasStatus::kUnsupportedArea, ""_span);
return;
}
// Checking for duplicate locations.
uint32_t j = 0;
auto jAreaIter = req.newAreas.begin();
while (j < i)
{
jAreaIter.Next(); // Since j < i and i is valid, we can safely call Next() without checking the return value.
if (jAreaIter.GetValue() == aSelectedArea)
{
exitResponse(SelectAreasStatus::kDuplicatedAreas, ""_span);
return;
}
j += 1;
}
// check to see if parameter list and attribute still match
if (matchesCurrentSelectedAreas)
{
if (!mDelegate->GetSelectedAreaByIndex(ignoredIndex, oldSelectedArea) || (aSelectedArea != oldSelectedArea))
{
matchesCurrentSelectedAreas = false;
}
}
i += 1;
}
// after iterating with Next through DecodableType - check for failure
if (CHIP_NO_ERROR != iAreaIter.GetStatus())
{
ctx.mCommandHandler.AddStatus(ctx.mRequestPath, Status::InvalidCommand);
return;
}
}
}
// 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(req, 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.
mDelegate->ClearSelectedAreas();
if (numberOfAreas != 0)
{
auto locationIter = req.newAreas.begin();
uint32_t ignored;
while (locationIter.Next())
{
mDelegate->AddSelectedArea(locationIter.GetValue(), 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);
};
// The SkippedArea field SHALL match an entry in the SupportedAreas list.
// If the Status field is set to InvalidAreaList, the StatusText field SHALL be an empty string.
if (!IsSupportedArea(req.skippedArea))
{
ChipLogError(Zcl, "SkippedArea (%u) is not in the SupportedAreas attribute.", req.skippedArea);
exitResponse(SkipAreaStatus::kInvalidAreaList, ""_span);
return;
}
// If the CurrentArea attribute is null, the status should be set to InvalidInMode.
// If the Status field is not set to Success, or InvalidAreaList, the StatusText field SHALL include a vendor defined error
// description.
if (mCurrentArea.IsNull())
{
exitResponse(SkipAreaStatus::kInvalidInMode, "Current Area attribute is null"_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::IsSupportedArea(uint32_t aAreaId)
{
uint32_t ignoredIndex;
AreaStructureWrapper ignoredArea;
return mDelegate->GetSupportedAreaById(aAreaId, ignoredIndex, ignoredArea);
}
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.areaDesc.locationInfo.IsNull() && aArea.areaDesc.landmarkInfo.IsNull())
{
ChipLogDetail(Zcl, "IsValidAsSupportedArea %u - 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.areaDesc.locationInfo.IsNull())
{
if (aArea.areaDesc.locationInfo.Value().locationName.empty() && aArea.areaDesc.locationInfo.Value().floorNumber.IsNull() &&
aArea.areaDesc.locationInfo.Value().areaType.IsNull() && aArea.areaDesc.landmarkInfo.IsNull())
{
ChipLogDetail(Zcl, "IsValidAsSupportedArea %u - 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) && (mDelegate->GetNumberOfSupportedMaps() > 0))
{
if (aArea.mapID.IsNull())
{
ChipLogDetail(Zcl, "IsValidSupportedArea %u - 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 (!IsSupportedMap(aArea.mapID.Value()))
{
ChipLogError(Zcl, "IsValidSupportedArea %u - map Id %u is not in supported map list", aArea.areaID,
aArea.mapID.Value());
return false;
}
}
else
{
if (!aArea.mapID.IsNull())
{
ChipLogDetail(Zcl, "IsValidSupportedArea %u - map Id %u 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 (mDelegate->GetNumberOfSupportedMaps() == 0)
{
config.Set(AreaStructureWrapper::IsEqualConfig::kIgnoreMapId);
}
uint8_t locationIndex = 0;
AreaStructureWrapper entry;
while (mDelegate->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());
}
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 (mDelegate->GetNumberOfSupportedAreas() >= kMaxNumSupportedAreas)
{
ChipLogError(Zcl, "AddSupportedArea %u - too many entries", aNewArea.areaID);
return false;
}
// Verify cluster requirements concerning valid fields and field relationships.
if (!IsValidSupportedArea(aNewArea))
{
ChipLogError(Zcl, "AddSupportedArea %u - 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, "AddSupportedArea %u - not a unique location object", aNewArea.areaID);
return false;
}
// Add the SupportedArea to the SupportedAreas attribute.
uint32_t ignoredIndex;
if (!mDelegate->AddSupportedArea(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 (!mDelegate->GetSupportedAreaById(aNewArea.areaID, listIndex, supportedArea))
{
ChipLogError(Zcl, "ModifySupportedArea %u - 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, "ModifySupportedArea %u - 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, "ModifySupportedArea %u - not a unique location object", aNewArea.areaID);
return false;
}
// Replace the supported location with the modified location.
if (!mDelegate->ModifySupportedArea(listIndex, aNewArea))
{
return false;
}
}
if (mapIDChanged)
{
mDelegate->HandleSupportedAreasUpdated();
}
NotifySupportedAreasChanged();
return true;
}
bool Instance::ClearSupportedAreas()
{
// does device mode allow this attribute to be updated?
if (!mDelegate->IsSupportedAreasChangeAllowed())
{
return false;
}
if (mDelegate->ClearSupportedAreas())
{
mDelegate->HandleSupportedAreasUpdated();
NotifySupportedAreasChanged();
return true;
}
return false;
}
//*************************************************************************
// Supported Maps manipulators
bool Instance::IsSupportedMap(uint32_t aMapId)
{
uint32_t ignoredIndex;
MapStructureWrapper ignoredMap;
return mDelegate->GetSupportedMapById(aMapId, ignoredIndex, ignoredMap);
}
bool Instance::AddSupportedMap(uint32_t aMapId, const CharSpan & aMapName)
{
// check max# of list entries
if (mDelegate->GetNumberOfSupportedMaps() >= kMaxNumSupportedMaps)
{
ChipLogError(Zcl, "AddSupportedMap %u - 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, "AddSupportedMap %u - 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 (mDelegate->GetSupportedMapByIndex(mapIndex++, entry))
{
// the name cannot be the same as an existing map
if (entry.IsNameEqual(aMapName))
{
ChipLogError(Zcl, "AddSupportedMap %u - 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, "AddSupportedMap - non-unique Id %u", aMapId);
return false;
}
}
{
// add to supported maps attribute
MapStructureWrapper newMap(aMapId, aMapName);
uint32_t ignoredIndex;
if (!mDelegate->AddSupportedMap(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 (!mDelegate->GetSupportedMapById(aMapId, modifiedIndex, modifiedMap))
{
ChipLogError(Zcl, "RenameSupportedMap Id %u - 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 %u - 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 (mDelegate->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 %u - map already exists with same name '%.*s'", aMapId,
static_cast<int>(entry.GetName().size()), entry.GetName().data());
return false;
}
++loopIndex;
}
if (!mDelegate->ModifySupportedMap(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 (mDelegate->ClearSupportedMaps())
{
ClearSupportedAreas();
NotifySupportedMapsChanged();
return true;
}
return false;
}
//*************************************************************************
// Selected Areas manipulators
bool Instance::AddSelectedArea(uint32_t & aSelectedArea)
{
// check max# of list entries
if (mDelegate->GetNumberOfSelectedAreas() >= kMaxNumSelectedAreas)
{
ChipLogError(Zcl, "AddSelectedArea %u - 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 (!IsSupportedArea(aSelectedArea))
{
ChipLogError(Zcl, "AddSelectedArea %u - not a supported location", aSelectedArea);
return false;
}
// each entry in this list SHALL have a unique value
if (mDelegate->IsSelectedArea(aSelectedArea))
{
ChipLogError(Zcl, "AddSelectedArea %u - 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, "AddSelectedArea %u - %.*s", aSelectedArea, static_cast<int>(locationStatusText.size()),
locationStatusText.data());
return false;
}
uint32_t ignoredIndex;
return mDelegate->AddSelectedArea(aSelectedArea, ignoredIndex);
}
bool Instance::ClearSelectedAreas()
{
if (mDelegate->ClearSelectedAreas())
{
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()) && (!IsSupportedArea(aCurrentArea.Value())))
{
ChipLogError(Zcl, "SetCurrentArea %u - 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
bool Instance::AddPendingProgressElement(uint32_t aAreaId)
{
// create progress element
Structs::ProgressStruct::Type inactiveProgress = { aAreaId, OperationalStatusEnum::kPending };
// check max# of list entries
if (mDelegate->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 (!IsSupportedArea(aAreaId))
{
ChipLogError(Zcl, "AddPendingProgressElement - not a supported location %u", aAreaId);
return false;
}
// Each entry in this list SHALL have a unique value for the AreaID field.
if (mDelegate->IsProgressElement(aAreaId))
{
ChipLogError(Zcl, "AddPendingProgressElement - progress element already exists for location %u", aAreaId);
return false;
}
uint32_t ignoredIndex;
if (!mDelegate->AddProgressElement(inactiveProgress, ignoredIndex))
{
return false;
}
NotifyProgressChanged();
return true;
}
bool Instance::SetProgressStatus(uint32_t aAreaId, OperationalStatusEnum opStatus)
{
uint32_t listIndex;
Structs::ProgressStruct::Type progressElement;
if (!mDelegate->GetProgressElementById(aAreaId, listIndex, progressElement))
{
ChipLogError(Zcl, "SetProgressStatus - progress element does not exist for location %u", 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 (!mDelegate->ModifyProgressElement(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 (!mDelegate->GetProgressElementById(aAreaId, listIndex, progressElement))
{
ChipLogError(Zcl, "SetProgressTotalOperationalTime - progress element does not exist for location %u", 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 %u 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 (!mDelegate->ModifyProgressElement(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 (!mDelegate->GetProgressElementById(aAreaId, listIndex, progressElement))
{
ChipLogError(Zcl, "SetProgressEstimatedTime - progress element does not exist for location %u", 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 (!mDelegate->ModifyProgressElement(listIndex, progressElement))
{
return false;
}
NotifyProgressChanged();
return true;
}
bool Instance::ClearProgress()
{
if (mDelegate->ClearProgress())
{
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