/** * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 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( handlerContext, [this](HandlerContext & ctx, const auto & req) { HandleSelectAreasCmd(ctx, req); }); case Commands::SkipArea::Id: return CommandHandlerInterface::HandleCommand( 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(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 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 & 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 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 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(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(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 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(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 Instance::GetCurrentArea() { return mCurrentArea; } bool Instance::SetCurrentArea(const DataModel::Nullable & 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 Instance::GetEstimatedEndTime() { return mEstimatedEndTime; } bool Instance::SetEstimatedEndTime(const DataModel::Nullable & 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 & 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 & 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