| /** |
| * |
| * Copyright (c) 2025-2026 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 <algorithm> |
| #include <app/clusters/zone-management-server/ZoneManagementCluster.h> |
| #include <app/clusters/zone-management-server/zone-geometry.h> |
| #include <app/persistence/AttributePersistence.h> |
| #include <app/server-cluster/AttributeListBuilder.h> |
| #include <app/server-cluster/DefaultServerCluster.h> |
| #include <clusters/ZoneManagement/Metadata.h> |
| #include <cmath> |
| |
| using namespace chip::app::Clusters::ZoneManagement::Attributes; |
| using namespace chip::app::Clusters::ZoneManagement::Structs; |
| using namespace chip::Protocols::InteractionModel; |
| |
| namespace { |
| |
| size_t CountUserDefinedZones(const std::vector<chip::app::Clusters::ZoneManagement::ZoneInformationStorage> & zones) |
| { |
| return static_cast<size_t>( |
| std::count_if(zones.begin(), zones.end(), [](const chip::app::Clusters::ZoneManagement::ZoneInformationStorage & zone) { |
| return zone.zoneSource == chip::app::Clusters::ZoneManagement::ZoneSourceEnum::kUser; |
| })); |
| } |
| |
| } // namespace |
| |
| namespace chip { |
| namespace app { |
| namespace Clusters { |
| namespace ZoneManagement { |
| |
| ZoneManagementCluster::ZoneManagementCluster(const Context & context) : |
| DefaultServerCluster({ context.endpointId, ZoneManagement::Id }), mClusterContext(context), mDelegate(context.delegate) |
| { |
| mDelegate.SetZoneManagementCluster(this); |
| } |
| |
| ZoneManagementCluster::~ZoneManagementCluster() |
| { |
| mDelegate.SetZoneManagementCluster(nullptr); |
| } |
| |
| CHIP_ERROR ZoneManagementCluster::Startup(ServerClusterContext & context) |
| { |
| ReturnErrorOnFailure(ValidateConfiguration()); |
| if (mClusterContext.initialSensitivity.has_value()) |
| { |
| VerifyOrReturnError(*mClusterContext.initialSensitivity >= 1 && |
| *mClusterContext.initialSensitivity <= mClusterContext.config.sensitivityMax, |
| CHIP_IM_GLOBAL_STATUS(ConstraintError)); |
| } |
| |
| ReturnErrorOnFailure(DefaultServerCluster::Startup(context)); |
| |
| LoadPersistentAttributes(); |
| return CHIP_NO_ERROR; |
| } |
| |
| void ZoneManagementCluster::Shutdown(ClusterShutdownType shutdownType) |
| { |
| mZones.clear(); |
| mTriggers.clear(); |
| mUserDefinedZonesCount = 0; |
| |
| DefaultServerCluster::Shutdown(shutdownType); |
| } |
| |
| CHIP_ERROR ZoneManagementCluster::ValidateConfiguration() const |
| { |
| if (HasFeature(Feature::kUserDefined)) |
| { |
| VerifyOrReturnError(mClusterContext.config.maxUserDefinedZones >= 5, CHIP_ERROR_INVALID_ARGUMENT, |
| ChipLogError(Zcl, "ZoneManagement[ep=%d]: MaxUserDefinedZones configuration error", mPath.mEndpointId)); |
| VerifyOrReturnError(mClusterContext.config.maxZones >= mClusterContext.config.maxUserDefinedZones, |
| CHIP_ERROR_INVALID_ARGUMENT, |
| ChipLogError(Zcl, "ZoneManagement[ep=%d]: MaxZones configuration error", mPath.mEndpointId)); |
| } |
| else |
| { |
| VerifyOrReturnError(mClusterContext.config.maxZones >= 1, CHIP_ERROR_INVALID_ARGUMENT, |
| ChipLogError(Zcl, "ZoneManagement[ep=%d]: MaxZones configuration error", mPath.mEndpointId)); |
| } |
| |
| VerifyOrReturnError(mClusterContext.config.sensitivityMax >= 2 && mClusterContext.config.sensitivityMax <= 10, |
| CHIP_ERROR_INVALID_ARGUMENT, |
| ChipLogError(Zcl, "ZoneManagement[ep=%d]: SensitivityMax configuration error", mPath.mEndpointId)); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| bool ZoneManagementCluster::HasFeature(Feature feature) const |
| { |
| return mClusterContext.features.Has(feature); |
| } |
| |
| void ZoneManagementCluster::LoadPersistentAttributes() |
| { |
| VerifyOrReturn(mContext != nullptr); |
| |
| mZones.clear(); |
| mTriggers.clear(); |
| mUserDefinedZonesCount = 0; |
| |
| // Load Zones |
| CHIP_ERROR err = mDelegate.LoadZones(mZones); |
| if (err != CHIP_NO_ERROR) |
| { |
| ChipLogDetail(Zcl, "ZoneManagement[ep=%d]: Unable to load zones from storage.", mPath.mEndpointId); |
| } |
| |
| // Load Triggers |
| err = mDelegate.LoadTriggers(mTriggers); |
| if (err != CHIP_NO_ERROR) |
| { |
| ChipLogDetail(Zcl, "ZoneManagement[ep=%d]: Unable to load triggers from storage.", mPath.mEndpointId); |
| } |
| |
| const size_t userZonesCount = CountUserDefinedZones(mZones); |
| mUserDefinedZonesCount = static_cast<uint8_t>(std::min<size_t>(userZonesCount, mClusterContext.config.maxUserDefinedZones)); |
| |
| // Load Sensitivity |
| const ConcreteAttributePath sensitivityPath(mPath.mEndpointId, mPath.mClusterId, Sensitivity::Id); |
| AttributePersistence persistence(mContext->attributeStorage); |
| |
| if (mClusterContext.initialSensitivity.has_value()) |
| { |
| mSensitivity = *mClusterContext.initialSensitivity; |
| const ByteSpan value(reinterpret_cast<const uint8_t *>(&mSensitivity), sizeof(mSensitivity)); |
| LogErrorOnFailure(mContext->attributeStorage.WriteValue(sensitivityPath, value)); |
| } |
| else |
| { |
| uint8_t loadedSensitivity = mSensitivity; |
| if (persistence.LoadNativeEndianValue(sensitivityPath, loadedSensitivity, mSensitivity)) |
| { |
| if (loadedSensitivity >= 1 && loadedSensitivity <= mClusterContext.config.sensitivityMax) |
| { |
| mSensitivity = loadedSensitivity; |
| ChipLogDetail(Zcl, "ZoneManagement[ep=%d]: Loaded Sensitivity as %u", mPath.mEndpointId, mSensitivity); |
| } |
| else |
| { |
| ChipLogDetail(Zcl, "ZoneManagement[ep=%d]: Loaded invalid Sensitivity=%u. Keeping current value=%u", |
| mPath.mEndpointId, loadedSensitivity, mSensitivity); |
| } |
| } |
| else |
| { |
| ChipLogDetail(Zcl, "ZoneManagement[ep=%d]: Sensitivity not found in storage. Using %u", mPath.mEndpointId, |
| mSensitivity); |
| } |
| } |
| |
| // Signal delegate that all persistent configuration attributes have been loaded. |
| LogErrorOnFailure(mDelegate.PersistentAttributesLoadedCallback()); |
| } |
| |
| CHIP_ERROR ZoneManagementCluster::ReadAndEncodeZones(const AttributeValueEncoder::ListEncodeHelper & encoder) |
| { |
| for (const auto & zone : mZones) |
| { |
| ReturnErrorOnFailure(encoder.Encode(zone)); |
| } |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ZoneManagementCluster::ReadAndEncodeTriggers(const AttributeValueEncoder::ListEncodeHelper & encoder) |
| { |
| for (const auto & trigger : mTriggers) |
| { |
| ReturnErrorOnFailure(encoder.Encode(trigger)); |
| } |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| Optional<ZoneTriggerControlStruct> ZoneManagementCluster::GetTriggerForZone(uint16_t zoneID) const |
| { |
| auto foundTrigger = std::find_if(mTriggers.begin(), mTriggers.end(), |
| [&](const ZoneTriggerControlStruct & zoneTrigger) { return zoneTrigger.zoneID == zoneID; }); |
| |
| if (foundTrigger == mTriggers.end()) |
| { |
| return NullOptional; |
| } |
| |
| return MakeOptional(*foundTrigger); |
| } |
| |
| CHIP_ERROR ZoneManagementCluster::SetSensitivity(uint8_t aSensitivity) |
| { |
| if (aSensitivity < 1 || aSensitivity > mClusterContext.config.sensitivityMax) |
| { |
| return CHIP_IM_GLOBAL_STATUS(ConstraintError); |
| } |
| |
| VerifyOrReturnValue(SetAttributeValue(mSensitivity, aSensitivity, Sensitivity::Id), CHIP_NO_ERROR); |
| |
| if (mContext != nullptr) |
| { |
| const ConcreteAttributePath path(mPath.mEndpointId, mPath.mClusterId, Sensitivity::Id); |
| const ByteSpan value(reinterpret_cast<const uint8_t *>(&mSensitivity), sizeof(mSensitivity)); |
| LogErrorOnFailure(mContext->attributeStorage.WriteValue(path, value)); |
| } |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| DataModel::ActionReturnStatus ZoneManagementCluster::ReadAttribute(const DataModel::ReadAttributeRequest & request, |
| AttributeValueEncoder & encoder) |
| { |
| switch (request.path.mAttributeId) |
| { |
| case FeatureMap::Id: |
| return encoder.Encode(mClusterContext.features); |
| case ClusterRevision::Id: |
| return encoder.Encode(kRevision); |
| case MaxUserDefinedZones::Id: |
| return encoder.Encode(mClusterContext.config.maxUserDefinedZones); |
| case MaxZones::Id: |
| return encoder.Encode(mClusterContext.config.maxZones); |
| case Zones::Id: |
| return encoder.EncodeList([this](const auto & listEncoder) -> CHIP_ERROR { return ReadAndEncodeZones(listEncoder); }); |
| case Triggers::Id: |
| return encoder.EncodeList([this](const auto & listEncoder) -> CHIP_ERROR { return ReadAndEncodeTriggers(listEncoder); }); |
| case SensitivityMax::Id: |
| return encoder.Encode(mClusterContext.config.sensitivityMax); |
| case Sensitivity::Id: |
| return encoder.Encode(mSensitivity); |
| case TwoDCartesianMax::Id: |
| return encoder.Encode(mClusterContext.config.twoDCartesianMax); |
| } |
| |
| return Status::UnsupportedRead; |
| } |
| |
| DataModel::ActionReturnStatus ZoneManagementCluster::WriteAttribute(const DataModel::WriteAttributeRequest & request, |
| AttributeValueDecoder & decoder) |
| { |
| switch (request.path.mAttributeId) |
| { |
| case Sensitivity::Id: { |
| uint8_t sensitivity = 0; |
| ReturnErrorOnFailure(decoder.Decode(sensitivity)); |
| VerifyOrReturnError(sensitivity >= 1 && sensitivity <= mClusterContext.config.sensitivityMax, Status::ConstraintError); |
| VerifyOrReturnError(mContext != nullptr, CHIP_ERROR_INCORRECT_STATE); |
| VerifyOrReturnValue(sensitivity != mSensitivity, DataModel::ActionReturnStatus::FixedStatus::kWriteSuccessNoOp); |
| |
| const ConcreteAttributePath path(request.path.mEndpointId, request.path.mClusterId, request.path.mAttributeId); |
| CHIP_ERROR err = mContext->attributeStorage.WriteValue( |
| path, ByteSpan(reinterpret_cast<const uint8_t *>(&sensitivity), sizeof(sensitivity))); |
| if (err != CHIP_NO_ERROR) |
| { |
| ChipLogError(DataManagement, "ZoneManagement[ep=%d]: Failed to persist sensitivity: %" CHIP_ERROR_FORMAT, |
| mPath.mEndpointId, err.Format()); |
| return err; |
| } |
| |
| SetAttributeValue(mSensitivity, sensitivity, Sensitivity::Id); |
| mDelegate.OnAttributeChanged(request.path.mAttributeId); |
| |
| NotifyAttributeChanged(request.path.mAttributeId); |
| return Status::Success; |
| } |
| } |
| |
| return Status::UnsupportedWrite; |
| } |
| |
| CHIP_ERROR ZoneManagementCluster::Attributes(const ConcreteClusterPath & path, |
| ReadOnlyBufferBuilder<DataModel::AttributeEntry> & builder) |
| { |
| const AttributeListBuilder::OptionalAttributeEntry optionalAttributes[] = { |
| { HasFeature(Feature::kUserDefined), MaxUserDefinedZones::kMetadataEntry }, |
| { !HasFeature(Feature::kPerZoneSensitivity), Sensitivity::kMetadataEntry }, |
| { HasFeature(Feature::kTwoDimensionalCartesianZone), TwoDCartesianMax::kMetadataEntry }, |
| }; |
| |
| AttributeListBuilder listBuilder(builder); |
| return listBuilder.Append(Span(kMandatoryMetadata), Span(optionalAttributes)); |
| } |
| |
| CHIP_ERROR ZoneManagementCluster::AcceptedCommands(const ConcreteClusterPath & path, |
| ReadOnlyBufferBuilder<DataModel::AcceptedCommandEntry> & builder) |
| { |
| if (HasFeature(Feature::kTwoDimensionalCartesianZone) && HasFeature(Feature::kUserDefined)) |
| { |
| ReturnErrorOnFailure(builder.AppendElements( |
| { Commands::CreateTwoDCartesianZone::kMetadataEntry, Commands::UpdateTwoDCartesianZone::kMetadataEntry })); |
| } |
| |
| if (HasFeature(Feature::kUserDefined)) |
| { |
| ReturnErrorOnFailure(builder.AppendElements({ Commands::RemoveZone::kMetadataEntry })); |
| } |
| |
| return builder.AppendElements({ Commands::CreateOrUpdateTrigger::kMetadataEntry, Commands::RemoveTrigger::kMetadataEntry }); |
| } |
| |
| CHIP_ERROR ZoneManagementCluster::GeneratedCommands(const ConcreteClusterPath & path, ReadOnlyBufferBuilder<CommandId> & builder) |
| { |
| if (HasFeature(Feature::kTwoDimensionalCartesianZone) && HasFeature(Feature::kUserDefined)) |
| { |
| ReturnErrorOnFailure(builder.AppendElements({ Commands::CreateTwoDCartesianZoneResponse::Id })); |
| } |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| std::optional<DataModel::ActionReturnStatus> ZoneManagementCluster::InvokeCommand(const DataModel::InvokeRequest & request, |
| TLV::TLVReader & input_arguments, |
| CommandHandler * handler) |
| { |
| // Feature-gated command support is enforced by AcceptedCommands() during IM |
| // pre-dispatch validation (ValidateCommandCanBeDispatched). |
| switch (request.path.mCommandId) |
| { |
| case Commands::CreateTwoDCartesianZone::Id: { |
| Commands::CreateTwoDCartesianZone::DecodableType commandData; |
| ReturnErrorOnFailure(commandData.Decode(input_arguments)); |
| return HandleCreateTwoDCartesianZone(request.path, handler, commandData); |
| } |
| case Commands::UpdateTwoDCartesianZone::Id: { |
| Commands::UpdateTwoDCartesianZone::DecodableType commandData; |
| ReturnErrorOnFailure(commandData.Decode(input_arguments)); |
| return HandleUpdateTwoDCartesianZone(commandData); |
| } |
| case Commands::RemoveZone::Id: { |
| Commands::RemoveZone::DecodableType commandData; |
| ReturnErrorOnFailure(commandData.Decode(input_arguments)); |
| return HandleRemoveZone(commandData); |
| } |
| case Commands::CreateOrUpdateTrigger::Id: { |
| Commands::CreateOrUpdateTrigger::DecodableType commandData; |
| ReturnErrorOnFailure(commandData.Decode(input_arguments)); |
| return HandleCreateOrUpdateTrigger(commandData); |
| } |
| case Commands::RemoveTrigger::Id: { |
| Commands::RemoveTrigger::DecodableType commandData; |
| ReturnErrorOnFailure(commandData.Decode(input_arguments)); |
| return HandleRemoveTrigger(commandData); |
| } |
| default: |
| return Status::UnsupportedCommand; |
| } |
| } |
| |
| CHIP_ERROR ZoneManagementCluster::AddZone(const ZoneInformationStorage & zone) |
| { |
| mZones.push_back(zone); |
| NotifyAttributeChanged(Attributes::Zones::Id); |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ZoneManagementCluster::UpdateZone(uint16_t zoneId, const ZoneInformationStorage & zoneInfo) |
| { |
| auto it = |
| std::find_if(mZones.begin(), mZones.end(), [zoneId](const ZoneInformationStorage & zone) { return zone.zoneID == zoneId; }); |
| |
| if (it == mZones.end()) |
| { |
| return CHIP_ERROR_NOT_FOUND; |
| } |
| |
| *it = zoneInfo; |
| NotifyAttributeChanged(Attributes::Zones::Id); |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ZoneManagementCluster::RemoveZone(uint16_t zoneId) |
| { |
| auto it = |
| std::remove_if(mZones.begin(), mZones.end(), [&](const ZoneInformationStorage & zone) { return zone.zoneID == zoneId; }); |
| |
| if (it == mZones.end()) |
| { |
| return CHIP_ERROR_NOT_FOUND; |
| } |
| |
| mZones.erase(it, mZones.end()); |
| NotifyAttributeChanged(Attributes::Zones::Id); |
| return CHIP_NO_ERROR; |
| } |
| |
| Status ZoneManagementCluster::AddOrUpdateTrigger(const ZoneTriggerControlStruct & trigger) |
| { |
| auto foundTrigger = std::find_if(mTriggers.begin(), mTriggers.end(), [&](const ZoneTriggerControlStruct & zoneTrigger) { |
| return zoneTrigger.zoneID == trigger.zoneID; |
| }); |
| |
| Status status = Status::Failure; |
| if (foundTrigger == mTriggers.end()) |
| { |
| status = mDelegate.CreateTrigger(trigger); |
| if (status == Status::Success) |
| { |
| mTriggers.push_back(trigger); |
| } |
| } |
| else |
| { |
| status = mDelegate.UpdateTrigger(trigger); |
| if (status == Status::Success) |
| { |
| *foundTrigger = trigger; |
| } |
| } |
| |
| NotifyAttributeChangedIfSuccess(Attributes::Triggers::Id, status); |
| |
| return status; |
| } |
| |
| Status ZoneManagementCluster::RemoveTrigger(uint16_t zoneId) |
| { |
| Status status = mDelegate.RemoveTrigger(zoneId); |
| if (status == Status::Success) |
| { |
| mTriggers.erase(std::remove_if(mTriggers.begin(), mTriggers.end(), |
| [&](const ZoneTriggerControlStruct & trigger) { return trigger.zoneID == zoneId; }), |
| mTriggers.end()); |
| NotifyAttributeChanged(Attributes::Triggers::Id); |
| } |
| |
| return status; |
| } |
| |
| Status ZoneManagementCluster::ValidateTwoDCartesianZone(const TwoDCartesianZoneDecodableStruct & zone) |
| { |
| VerifyOrReturnError(zone.name.size() <= 32, Status::ConstraintError); |
| VerifyOrReturnError(zone.use != ZoneUseEnum::kUnknownEnumValue, Status::ConstraintError); |
| |
| size_t listCount = 0; |
| VerifyOrReturnError(zone.vertices.ComputeSize(&listCount) == CHIP_NO_ERROR, Status::InvalidCommand); |
| VerifyOrReturnError(listCount >= 3 && listCount <= 12, Status::ConstraintError); |
| |
| if (zone.color.HasValue()) |
| { |
| VerifyOrReturnError(zone.color.Value().size() == 7 || zone.color.Value().size() == 9, Status::ConstraintError); |
| } |
| |
| return Status::Success; |
| } |
| |
| std::optional<DataModel::ActionReturnStatus> |
| ZoneManagementCluster::HandleCreateTwoDCartesianZone(const ConcreteCommandPath & requestPath, CommandHandler * handler, |
| const Commands::CreateTwoDCartesianZone::DecodableType & commandData) |
| { |
| uint16_t zoneID = 0; |
| const auto & zoneToCreate = commandData.zone; |
| |
| Status status = ValidateTwoDCartesianZone(zoneToCreate); |
| VerifyOrReturnValue(status == Status::Success, status); |
| |
| VerifyOrReturnValue(zoneToCreate.use != ZoneUseEnum::kFocus || HasFeature(Feature::kFocusZones), Status::ConstraintError); |
| VerifyOrReturnValue(mUserDefinedZonesCount < mClusterContext.config.maxUserDefinedZones, Status::ResourceExhausted); |
| |
| std::vector<TwoDCartesianVertexStruct> twoDCartVertices; |
| |
| auto iter = zoneToCreate.vertices.begin(); |
| while (iter.Next()) |
| { |
| auto vertex = iter.GetValue(); |
| if (vertex.x > mClusterContext.config.twoDCartesianMax.x || vertex.y > mClusterContext.config.twoDCartesianMax.y) |
| { |
| return Status::DynamicConstraintError; |
| } |
| twoDCartVertices.push_back(vertex); |
| } |
| |
| CHIP_ERROR err = iter.GetStatus(); |
| if (err != CHIP_NO_ERROR) |
| { |
| ChipLogError(Zcl, "CreateTwoDCartesianZone: Vertices list error: %" CHIP_ERROR_FORMAT, err.Format()); |
| return Status::InvalidCommand; |
| } |
| |
| if (ZoneAlreadyExists(zoneToCreate.use, twoDCartVertices, DataModel::NullNullable)) |
| { |
| return Status::AlreadyExists; |
| } |
| |
| if (ZoneGeometry::IsZoneSelfIntersecting(twoDCartVertices)) |
| { |
| ChipLogError(Zcl, "CreateTwoDCartesianZone: Found self-intersecting polygon vertices for zone"); |
| return Status::DynamicConstraintError; |
| } |
| |
| TwoDCartesianZoneStorage twoDCartZoneStorage; |
| twoDCartZoneStorage.Set(zoneToCreate.name, zoneToCreate.use, twoDCartVertices, zoneToCreate.color); |
| |
| status = mDelegate.CreateTwoDCartesianZone(twoDCartZoneStorage, zoneID); |
| VerifyOrReturnValue(status == Status::Success, status); |
| |
| ZoneInformationStorage zoneInfo; |
| zoneInfo.Set(zoneID, ZoneTypeEnum::kTwoDCARTZone, ZoneSourceEnum::kUser, MakeOptional(twoDCartZoneStorage)); |
| ReturnErrorOnFailure(AddZone(zoneInfo)); |
| mUserDefinedZonesCount++; |
| |
| Commands::CreateTwoDCartesianZoneResponse::Type response; |
| response.zoneID = zoneID; |
| handler->AddResponse(requestPath, response); |
| return std::nullopt; |
| } |
| |
| DataModel::ActionReturnStatus |
| ZoneManagementCluster::HandleUpdateTwoDCartesianZone(const Commands::UpdateTwoDCartesianZone::DecodableType & commandData) |
| { |
| const uint16_t zoneID = commandData.zoneID; |
| const auto & zoneToUpdate = commandData.zone; |
| |
| Status status = ValidateTwoDCartesianZone(zoneToUpdate); |
| VerifyOrReturnValue(status == Status::Success, status); |
| |
| auto foundZone = |
| std::find_if(mZones.begin(), mZones.end(), [&](const ZoneInformationStruct & z) { return z.zoneID == zoneID; }); |
| VerifyOrReturnValue(foundZone != mZones.end(), Status::NotFound, |
| ChipLogError(Zcl, "ZoneMgmt[ep=%d]: No zone exists by id %d", mPath.mEndpointId, zoneID)); |
| |
| VerifyOrReturnValue(foundZone->zoneSource != ZoneSourceEnum::kMfg, Status::ConstraintError); |
| VerifyOrReturnValue(foundZone->zoneType == ZoneTypeEnum::kTwoDCARTZone, Status::DynamicConstraintError); |
| |
| std::vector<TwoDCartesianVertexStruct> twoDCartVertices; |
| |
| auto iter = zoneToUpdate.vertices.begin(); |
| while (iter.Next()) |
| { |
| auto vertex = iter.GetValue(); |
| if (vertex.x > mClusterContext.config.twoDCartesianMax.x || vertex.y > mClusterContext.config.twoDCartesianMax.y) |
| { |
| return Status::DynamicConstraintError; |
| } |
| twoDCartVertices.push_back(vertex); |
| } |
| |
| CHIP_ERROR err = iter.GetStatus(); |
| if (err != CHIP_NO_ERROR) |
| { |
| ChipLogError(Zcl, "UpdateTwoDCartesianZone: Vertices list error: %" CHIP_ERROR_FORMAT, err.Format()); |
| return Status::InvalidCommand; |
| } |
| |
| if (ZoneAlreadyExists(zoneToUpdate.use, twoDCartVertices, DataModel::MakeNullable(zoneID))) |
| { |
| return Status::AlreadyExists; |
| } |
| |
| if (ZoneGeometry::IsZoneSelfIntersecting(twoDCartVertices)) |
| { |
| ChipLogError(Zcl, "UpdateTwoDCartesianZone: Found self-intersecting polygon vertices for zone"); |
| return Status::DynamicConstraintError; |
| } |
| |
| TwoDCartesianZoneStorage twoDCartZoneStorage; |
| twoDCartZoneStorage.Set(zoneToUpdate.name, zoneToUpdate.use, twoDCartVertices, zoneToUpdate.color); |
| |
| status = mDelegate.UpdateTwoDCartesianZone(zoneID, twoDCartZoneStorage); |
| VerifyOrReturnValue(status == Status::Success, status); |
| |
| ZoneInformationStorage zoneInfo; |
| zoneInfo.Set(zoneID, ZoneTypeEnum::kTwoDCARTZone, ZoneSourceEnum::kUser, MakeOptional(twoDCartZoneStorage)); |
| ReturnErrorOnFailure(UpdateZone(zoneID, zoneInfo)); |
| |
| return Status::Success; |
| } |
| |
| DataModel::ActionReturnStatus ZoneManagementCluster::HandleRemoveZone(const Commands::RemoveZone::DecodableType & commandData) |
| { |
| const uint16_t zoneID = commandData.zoneID; |
| |
| auto foundZone = |
| std::find_if(mZones.begin(), mZones.end(), [&](const ZoneInformationStruct & z) { return z.zoneID == zoneID; }); |
| VerifyOrReturnValue(foundZone != mZones.end(), Status::NotFound, |
| ChipLogError(Zcl, "ZoneMgmt[ep=%d]: No zone exists by id %d", mPath.mEndpointId, zoneID)); |
| |
| VerifyOrReturnValue(foundZone->zoneSource == ZoneSourceEnum::kUser, Status::ConstraintError); |
| |
| auto foundTrigger = |
| std::find_if(mTriggers.begin(), mTriggers.end(), [&](const ZoneTriggerControlStruct & t) { return t.zoneID == zoneID; }); |
| VerifyOrReturnValue(foundTrigger == mTriggers.end(), Status::InvalidInState, |
| ChipLogError(Zcl, "ZoneMgmt[ep=%d]: Zone id %d is referenced in zone triggers", mPath.mEndpointId, zoneID)); |
| |
| Status status = mDelegate.RemoveZone(zoneID); |
| VerifyOrReturnValue(status == Status::Success, status); |
| |
| ReturnErrorOnFailure(RemoveZone(zoneID)); |
| if (mUserDefinedZonesCount > 0) |
| { |
| mUserDefinedZonesCount--; |
| } |
| |
| return Status::Success; |
| } |
| |
| Status ZoneManagementCluster::ValidateTrigger(const ZoneTriggerControlStruct & trigger) |
| { |
| VerifyOrReturnError(trigger.initialDuration >= 1, Status::ConstraintError); |
| VerifyOrReturnError(trigger.augmentationDuration <= trigger.initialDuration, Status::ConstraintError); |
| VerifyOrReturnError(trigger.maxDuration >= trigger.initialDuration, Status::ConstraintError); |
| |
| VerifyOrReturnValue(HasFeature(Feature::kPerZoneSensitivity) == trigger.sensitivity.HasValue(), Status::InvalidCommand); |
| if (trigger.sensitivity.HasValue()) |
| { |
| VerifyOrReturnError(trigger.sensitivity.Value() >= 1 && |
| trigger.sensitivity.Value() <= mClusterContext.config.sensitivityMax, |
| Status::ConstraintError); |
| } |
| |
| return Status::Success; |
| } |
| |
| DataModel::ActionReturnStatus |
| ZoneManagementCluster::HandleCreateOrUpdateTrigger(const Commands::CreateOrUpdateTrigger::DecodableType & commandData) |
| { |
| ZoneTriggerControlStruct trigger = commandData.trigger; |
| |
| Status status = ValidateTrigger(trigger); |
| VerifyOrReturnValue(status == Status::Success, status); |
| |
| auto foundZone = |
| std::find_if(mZones.begin(), mZones.end(), [&](const ZoneInformationStorage & z) { return z.zoneID == trigger.zoneID; }); |
| VerifyOrReturnValue(foundZone != mZones.end(), Status::NotFound, |
| ChipLogError(Zcl, "ZoneMgmt[ep=%d]: No zone exists by id %d", mPath.mEndpointId, trigger.zoneID)); |
| |
| VerifyOrReturnValue(!foundZone->twoDCartZoneStorage.HasValue() || |
| foundZone->twoDCartZoneStorage.Value().use == ZoneUseEnum::kMotion, |
| Status::ConstraintError); |
| |
| return AddOrUpdateTrigger(trigger); |
| } |
| |
| DataModel::ActionReturnStatus ZoneManagementCluster::HandleRemoveTrigger(const Commands::RemoveTrigger::DecodableType & commandData) |
| { |
| const uint16_t zoneID = commandData.zoneID; |
| |
| auto foundTrigger = |
| std::find_if(mTriggers.begin(), mTriggers.end(), [&](const ZoneTriggerControlStruct & t) { return t.zoneID == zoneID; }); |
| VerifyOrReturnValue(foundTrigger != mTriggers.end(), Status::NotFound, |
| ChipLogError(Zcl, "ZoneMgmt[ep=%d]: Trigger for zone id %d does not exist", mPath.mEndpointId, zoneID)); |
| |
| return RemoveTrigger(zoneID); |
| } |
| |
| bool ZoneManagementCluster::DoZoneUseAndVerticesMatch(ZoneUseEnum use, const std::vector<TwoDCartesianVertexStruct> & vertices, |
| const TwoDCartesianZoneStorage & zone) |
| { |
| if (use != zone.use) |
| { |
| return false; |
| } |
| |
| const size_t inputVerticesCount = vertices.size(); |
| const size_t zoneVerticesCount = zone.vertices.size(); |
| |
| if (inputVerticesCount != zoneVerticesCount) |
| { |
| return false; |
| } |
| |
| return std::equal(vertices.begin(), vertices.end(), zone.verticesVector.begin(), |
| [](const TwoDCartesianVertexStruct & v1, const TwoDCartesianVertexStruct & v2) { |
| return ZoneGeometry::AreTwoDCartVerticesEqual(v1, v2); |
| }); |
| } |
| |
| bool ZoneManagementCluster::ZoneAlreadyExists(ZoneUseEnum zoneUse, const std::vector<TwoDCartesianVertexStruct> & vertices, |
| const DataModel::Nullable<uint16_t> & excludeZoneId) |
| { |
| for (auto & zone : mZones) |
| { |
| if (!excludeZoneId.IsNull() && zone.zoneID == excludeZoneId.Value()) |
| { |
| continue; |
| } |
| |
| if (zone.twoDCartZoneStorage.HasValue() && DoZoneUseAndVerticesMatch(zoneUse, vertices, zone.twoDCartZoneStorage.Value())) |
| { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| Status ZoneManagementCluster::GenerateZoneTriggeredEvent(uint16_t zoneID, ZoneEventTriggeredReasonEnum triggerReason) |
| { |
| VerifyOrReturnValue(mContext != nullptr, Status::Failure); |
| |
| Events::ZoneTriggered::Type event; |
| event.zone = zoneID; |
| event.reason = triggerReason; |
| |
| if (!mContext->interactionContext.eventsGenerator.GenerateEvent(event, mPath.mEndpointId).has_value()) |
| { |
| ChipLogError(AppServer, "Endpoint %d - Unable to generate ZoneTriggered event", mPath.mEndpointId); |
| return Status::Failure; |
| } |
| |
| return Status::Success; |
| } |
| |
| Status ZoneManagementCluster::GenerateZoneStoppedEvent(uint16_t zoneID, ZoneEventStoppedReasonEnum stopReason) |
| { |
| VerifyOrReturnValue(mContext != nullptr, Status::Failure); |
| |
| Events::ZoneStopped::Type event; |
| event.zone = zoneID; |
| event.reason = stopReason; |
| |
| if (!mContext->interactionContext.eventsGenerator.GenerateEvent(event, mPath.mEndpointId).has_value()) |
| { |
| ChipLogError(AppServer, "Endpoint %d - Unable to generate ZoneStopped event", mPath.mEndpointId); |
| return Status::Failure; |
| } |
| |
| return Status::Success; |
| } |
| |
| } // namespace ZoneManagement |
| } // namespace Clusters |
| } // namespace app |
| } // namespace chip |