| /** |
| * |
| * Copyright (c) 2025 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 <app/AttributeAccessInterfaceRegistry.h> |
| #include <app/CommandHandlerInterfaceRegistry.h> |
| #include <app/InteractionModelEngine.h> |
| #include <app/clusters/camera-av-settings-user-level-management-server/CameraAvSettingsUserLevelManagementCluster.h> |
| #include <app/persistence/AttributePersistenceProvider.h> |
| #include <app/reporting/reporting.h> |
| #include <app/server-cluster/AttributeListBuilder.h> |
| #include <app/util/util.h> |
| #include <clusters/CameraAvSettingsUserLevelManagement/Metadata.h> |
| #include <lib/core/CHIPSafeCasts.h> |
| #include <lib/support/DefaultStorageKeyAllocator.h> |
| #include <protocols/interaction_model/StatusCode.h> |
| |
| using namespace chip; |
| using namespace chip::app; |
| using namespace chip::app::Clusters; |
| using namespace chip::app::Clusters::CameraAvSettingsUserLevelManagement; |
| using namespace chip::app::Clusters::CameraAvSettingsUserLevelManagement::Structs; |
| using namespace chip::app::Clusters::CameraAvSettingsUserLevelManagement::Attributes; |
| using namespace Protocols::InteractionModel; |
| |
| namespace chip { |
| namespace app { |
| namespace Clusters { |
| |
| CameraAvSettingsUserLevelMgmtServerLogic::CameraAvSettingsUserLevelMgmtServerLogic(EndpointId aEndpointId, |
| BitFlags<Feature> aFeatures, |
| uint8_t aMaxPresets) : |
| mEndpointId(aEndpointId), |
| mFeatures(aFeatures), mMaxPresets(aMaxPresets) |
| {} |
| |
| CameraAvSettingsUserLevelMgmtServerLogic::~CameraAvSettingsUserLevelMgmtServerLogic() {} |
| |
| CHIP_ERROR CameraAvSettingsUserLevelMgmtServerLogic::Startup(AttributePersistenceProvider & aAttributePersistenceProvider) |
| { |
| mAttributePersistenceProvider = &aAttributePersistenceProvider; |
| ChipLogProgress(Zcl, "CameraAvSettingsUserLevelManagement: Startup"); |
| // Make sure mandated Features are set |
| // |
| VerifyOrReturnError(HasFeature(Feature::kMechanicalPan) || HasFeature(Feature::kMechanicalTilt) || |
| HasFeature(Feature::kMechanicalZoom) || HasFeature(Feature::kDigitalPTZ), |
| CHIP_ERROR_INVALID_ARGUMENT, |
| ChipLogError(Zcl, |
| "CameraAVSettingsUserLevelMgmt: Feature configuration error. At least one of " |
| "Mechanical Pan, Tilt, Zoom, or Digital PTZ must be supported")); |
| |
| LoadPersistentAttributes(); |
| |
| SetMovementState(PhysicalMovementEnum::kIdle); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| void CameraAvSettingsUserLevelMgmtServerLogic::Shutdown() |
| { |
| mDelegate->ShutdownApp(); |
| } |
| |
| bool CameraAvSettingsUserLevelMgmtServerLogic::HasFeature(Feature aFeature) const |
| { |
| return mFeatures.Has(aFeature); |
| } |
| |
| CHIP_ERROR |
| CameraAvSettingsUserLevelMgmtServerLogic::AcceptedCommands(ReadOnlyBufferBuilder<DataModel::AcceptedCommandEntry> & builder) |
| { |
| if ((HasFeature(Feature::kMechanicalPan)) || (HasFeature(Feature::kMechanicalTilt)) || (HasFeature(Feature::kMechanicalZoom))) |
| { |
| ReturnErrorOnFailure(builder.AppendElements({ Commands::MPTZSetPosition::kMetadataEntry })); |
| ReturnErrorOnFailure(builder.AppendElements({ Commands::MPTZRelativeMove::kMetadataEntry })); |
| } |
| |
| if (HasFeature(Feature::kMechanicalPresets)) |
| { |
| ReturnErrorOnFailure(builder.AppendElements({ Commands::MPTZMoveToPreset::kMetadataEntry })); |
| ReturnErrorOnFailure(builder.AppendElements({ Commands::MPTZSavePreset::kMetadataEntry })); |
| ReturnErrorOnFailure(builder.AppendElements({ Commands::MPTZRemovePreset::kMetadataEntry })); |
| } |
| |
| if (HasFeature(Feature::kDigitalPTZ)) |
| { |
| ReturnErrorOnFailure(builder.AppendElements({ Commands::DPTZSetViewport::kMetadataEntry })); |
| ReturnErrorOnFailure(builder.AppendElements({ Commands::DPTZRelativeMove::kMetadataEntry })); |
| } |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR CameraAvSettingsUserLevelMgmtServerLogic::Attributes(ReadOnlyBufferBuilder<DataModel::AttributeEntry> & builder) |
| { |
| AttributeListBuilder listBuilder(builder); |
| |
| // All attributes are set dependent on the Feature Flags |
| AttributeListBuilder::OptionalAttributeEntry optionalAttributes[] = { |
| { HasFeature(Feature::kMechanicalPan) || HasFeature(Feature::kMechanicalTilt) || HasFeature(Feature::kMechanicalZoom), |
| MPTZPosition::kMetadataEntry }, |
| { HasFeature(Feature::kMechanicalPresets), MaxPresets::kMetadataEntry }, |
| { HasFeature(Feature::kMechanicalPan), MPTZPresets::kMetadataEntry }, |
| { HasFeature(Feature::kDigitalPTZ), DPTZStreams::kMetadataEntry }, |
| { HasFeature(Feature::kMechanicalZoom), ZoomMax::kMetadataEntry }, |
| { HasFeature(Feature::kMechanicalTilt), TiltMin::kMetadataEntry }, |
| { HasFeature(Feature::kMechanicalTilt), TiltMax::kMetadataEntry }, |
| { HasFeature(Feature::kMechanicalPan), PanMin::kMetadataEntry }, |
| { HasFeature(Feature::kMechanicalPan), PanMax::kMetadataEntry }, |
| { HasFeature(Feature::kMechanicalPan) || HasFeature(Feature::kMechanicalTilt) || HasFeature(Feature::kMechanicalZoom), |
| MovementState::kMetadataEntry }, |
| }; |
| |
| return listBuilder.Append(Span(Attributes::kMandatoryMetadata), Span(optionalAttributes)); |
| } |
| |
| void CameraAvSettingsUserLevelMgmtServerLogic::MarkDirty(AttributeId aAttributeId) |
| { |
| if (mMarkDirtyCallback) |
| { |
| mMarkDirtyCallback(aAttributeId); |
| } |
| } |
| |
| CHIP_ERROR CameraAvSettingsUserLevelMgmtServerLogic::StoreMPTZPosition( |
| const CameraAvSettingsUserLevelManagement::Structs::MPTZStruct::Type & mptzPosition) |
| { |
| VerifyOrReturnError(mAttributePersistenceProvider != nullptr, CHIP_ERROR_INCORRECT_STATE); |
| uint8_t buffer[kMptzPositionStructMaxSerializedSize]; |
| MutableByteSpan bufferSpan(buffer); |
| TLV::TLVWriter writer; |
| |
| writer.Init(bufferSpan); |
| ReturnErrorOnFailure(mptzPosition.Encode(writer, TLV::AnonymousTag())); |
| |
| auto path = ConcreteAttributePath(mEndpointId, CameraAvSettingsUserLevelManagement::Id, Attributes::MPTZPosition::Id); |
| bufferSpan.reduce_size(writer.GetLengthWritten()); |
| return mAttributePersistenceProvider->WriteValue(path, bufferSpan); |
| } |
| |
| CHIP_ERROR CameraAvSettingsUserLevelMgmtServerLogic::LoadMPTZPosition( |
| CameraAvSettingsUserLevelManagement::Structs::MPTZStruct::Type & mptzPosition) |
| { |
| VerifyOrReturnError(mAttributePersistenceProvider != nullptr, CHIP_ERROR_INCORRECT_STATE); |
| uint8_t buffer[kMptzPositionStructMaxSerializedSize]; |
| MutableByteSpan bufferSpan(buffer); |
| |
| auto path = ConcreteAttributePath(mEndpointId, CameraAvSettingsUserLevelManagement::Id, Attributes::MPTZPosition::Id); |
| ReturnErrorOnFailure(mAttributePersistenceProvider->ReadValue(path, bufferSpan)); |
| |
| TLV::TLVReader reader; |
| |
| reader.Init(bufferSpan); |
| ReturnErrorOnFailure(reader.Next(TLV::AnonymousTag())); |
| ReturnErrorOnFailure(mptzPosition.Decode(reader)); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR CameraAvSettingsUserLevelMgmtServerLogic::StoreMPTZPresets() |
| { |
| VerifyOrReturnError(mAttributePersistenceProvider != nullptr, CHIP_ERROR_INCORRECT_STATE); |
| Platform::ScopedMemoryBuffer<uint8_t> presets; |
| MutableByteSpan bufferSpan; |
| size_t maxBufferSize = static_cast<size_t>(kMaxMPTZPresetStructSerializedSize * mMaxPresets); |
| |
| if (!presets.Alloc(maxBufferSize)) |
| { |
| return CHIP_ERROR_NO_MEMORY; |
| } |
| bufferSpan = MutableByteSpan{ presets.Get(), maxBufferSize }; |
| TLV::TLVWriter writer; |
| |
| writer.Init(bufferSpan); |
| TLV::TLVType arrayType; |
| ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Array, arrayType)); |
| |
| for (const auto & mptzPresets : mMptzPresetHelpers) |
| { |
| // Get the details to encode from the preset helper |
| // |
| MPTZPresetStruct::Type presetStruct; |
| std::string name = mptzPresets.GetName(); |
| uint8_t preset = mptzPresets.GetPresetID(); |
| presetStruct.presetID = preset; |
| presetStruct.name = CharSpan(name.c_str(), name.size()); |
| presetStruct.settings = mptzPresets.GetMptzPosition(); |
| ChipLogDetail(Zcl, "CameraAVSettingsUserLevelMgmt[ep=%d]: Persisting an instance of MPTZPresetStruct. ID = %d. Name = %s", |
| mEndpointId, presetStruct.presetID, NullTerminated(presetStruct.name).c_str()); |
| ReturnErrorOnFailure(presetStruct.Encode(writer, TLV::AnonymousTag())); |
| } |
| |
| ReturnErrorOnFailure(writer.EndContainer(arrayType)); |
| |
| auto path = ConcreteAttributePath(mEndpointId, CameraAvSettingsUserLevelManagement::Id, Attributes::MPTZPresets::Id); |
| bufferSpan.reduce_size(writer.GetLengthWritten()); |
| ReturnErrorOnFailure(mAttributePersistenceProvider->WriteValue(path, bufferSpan)); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR CameraAvSettingsUserLevelMgmtServerLogic::LoadMPTZPresets() |
| { |
| VerifyOrReturnError(mAttributePersistenceProvider != nullptr, CHIP_ERROR_INCORRECT_STATE); |
| Platform::ScopedMemoryBuffer<uint8_t> presets; |
| MutableByteSpan bufferSpan; |
| size_t maxBufferSize = static_cast<size_t>(kMaxMPTZPresetStructSerializedSize * mMaxPresets); |
| |
| if (!presets.Alloc(maxBufferSize)) |
| { |
| return CHIP_ERROR_NO_MEMORY; |
| } |
| bufferSpan = MutableByteSpan{ presets.Get(), maxBufferSize }; |
| |
| auto path = ConcreteAttributePath(mEndpointId, CameraAvSettingsUserLevelManagement::Id, Attributes::MPTZPresets::Id); |
| CHIP_ERROR err = mAttributePersistenceProvider->ReadValue(path, bufferSpan); |
| |
| if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) |
| { |
| ChipLogDetail(Zcl, "CameraAVSettingsUserLevelMgmt[ep=%d]: No persisted MPTZPresets.", mEndpointId); |
| mMptzPresetHelpers.clear(); |
| return CHIP_NO_ERROR; |
| } |
| |
| ReturnErrorOnFailure(err); |
| |
| TLV::TLVReader reader; |
| reader.Init(bufferSpan); |
| |
| ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Array, TLV::AnonymousTag())); |
| TLV::TLVType arrayType; |
| ReturnErrorOnFailure(reader.EnterContainer(arrayType)); |
| |
| mMptzPresetHelpers.clear(); |
| while ((err = reader.Next()) == CHIP_NO_ERROR) |
| { |
| MPTZPresetStruct::Type preset; |
| ReturnErrorOnFailure(DataModel::Decode(reader, preset)); |
| MPTZPresetHelper aMptzPresetHelper; |
| |
| aMptzPresetHelper.SetPresetID(preset.presetID); |
| aMptzPresetHelper.SetName(preset.name); |
| aMptzPresetHelper.SetMptzPosition(preset.settings); |
| mMptzPresetHelpers.push_back(aMptzPresetHelper); |
| } |
| |
| VerifyOrReturnError(err == CHIP_ERROR_END_OF_TLV, err); |
| |
| ReturnErrorOnFailure(reader.ExitContainer(arrayType)); |
| |
| return reader.VerifyEndOfContainer(); |
| } |
| |
| CHIP_ERROR CameraAvSettingsUserLevelMgmtServerLogic::StoreDPTZStreams() |
| { |
| VerifyOrReturnError(mAttributePersistenceProvider != nullptr, CHIP_ERROR_INCORRECT_STATE); |
| uint8_t buffer[kMaxDPTZStreamsSerializedSize]; |
| MutableByteSpan bufferSpan(buffer); |
| TLV::TLVWriter writer; |
| |
| writer.Init(bufferSpan); |
| TLV::TLVType arrayType; |
| ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Array, arrayType)); |
| for (const auto & dptzstream : mDptzStreams) |
| { |
| ReturnErrorOnFailure(dptzstream.Encode(writer, TLV::AnonymousTag())); |
| } |
| |
| ReturnErrorOnFailure(writer.EndContainer(arrayType)); |
| |
| auto path = ConcreteAttributePath(mEndpointId, CameraAvSettingsUserLevelManagement::Id, Attributes::DPTZStreams::Id); |
| bufferSpan.reduce_size(writer.GetLengthWritten()); |
| ReturnErrorOnFailure(mAttributePersistenceProvider->WriteValue(path, bufferSpan)); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR CameraAvSettingsUserLevelMgmtServerLogic::LoadDPTZStreams() |
| { |
| VerifyOrReturnError(mAttributePersistenceProvider != nullptr, CHIP_ERROR_INCORRECT_STATE); |
| uint8_t buffer[kMaxDPTZStreamsSerializedSize]; |
| MutableByteSpan bufferSpan(buffer); |
| |
| auto path = ConcreteAttributePath(mEndpointId, CameraAvSettingsUserLevelManagement::Id, Attributes::DPTZStreams::Id); |
| |
| CHIP_ERROR err = mAttributePersistenceProvider->ReadValue(path, bufferSpan); |
| |
| if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) |
| { |
| ChipLogDetail(Zcl, "CameraAVSettingsUserLevelMgmt[ep=%d]: No persisted DPTZStreams.", mEndpointId); |
| mDptzStreams.clear(); |
| return CHIP_NO_ERROR; |
| } |
| |
| ReturnErrorOnFailure(err); |
| |
| TLV::TLVReader reader; |
| reader.Init(bufferSpan); |
| |
| ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Array, TLV::AnonymousTag())); |
| TLV::TLVType arrayType; |
| ReturnErrorOnFailure(reader.EnterContainer(arrayType)); |
| |
| mDptzStreams.clear(); |
| while ((err = reader.Next()) == CHIP_NO_ERROR) |
| { |
| DPTZStruct::Type stream; |
| ReturnErrorOnFailure(DataModel::Decode(reader, stream)); |
| |
| mDptzStreams.push_back(stream); |
| } |
| |
| VerifyOrReturnError(err == CHIP_ERROR_END_OF_TLV, err); |
| |
| ReturnErrorOnFailure(reader.ExitContainer(arrayType)); |
| |
| return reader.VerifyEndOfContainer(); |
| } |
| |
| /** |
| * Attribute mutators. In all cases, given that these may not have been enabled depending on the Feature Flags that are set, |
| * the associated Feature presence is checked. The attributes are updated, and marked dirty, only if there is a change in the |
| * attribute value after constraint checking has been completed. |
| */ |
| CHIP_ERROR CameraAvSettingsUserLevelMgmtServerLogic::SetTiltMin(int16_t aTiltMin) |
| { |
| VerifyOrReturnError(HasFeature(Feature::kMechanicalTilt), CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute)); |
| VerifyOrReturnError((aTiltMin >= kTiltMinMinValue) && (aTiltMin <= kTiltMinMaxValue), CHIP_IM_GLOBAL_STATUS(ConstraintError)); |
| |
| if (aTiltMin != mTiltMin) |
| { |
| mTiltMin = aTiltMin; |
| MarkDirty(Attributes::TiltMin::Id); |
| } |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR CameraAvSettingsUserLevelMgmtServerLogic::SetTiltMax(int16_t aTiltMax) |
| { |
| VerifyOrReturnError(HasFeature(Feature::kMechanicalTilt), CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute)); |
| VerifyOrReturnError((aTiltMax <= kTiltMaxMaxValue) && (aTiltMax >= kTiltMaxMinValue), CHIP_IM_GLOBAL_STATUS(ConstraintError)); |
| |
| if (aTiltMax != mTiltMax) |
| { |
| mTiltMax = aTiltMax; |
| MarkDirty(Attributes::TiltMax::Id); |
| } |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR CameraAvSettingsUserLevelMgmtServerLogic::SetPanMin(int16_t aPanMin) |
| { |
| VerifyOrReturnError(HasFeature(Feature::kMechanicalPan), CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute)); |
| VerifyOrReturnError((aPanMin >= kPanMinMinValue) && (aPanMin <= kPanMinMaxValue), CHIP_IM_GLOBAL_STATUS(ConstraintError)); |
| |
| if (aPanMin != mPanMin) |
| { |
| mPanMin = aPanMin; |
| MarkDirty(Attributes::PanMin::Id); |
| } |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR CameraAvSettingsUserLevelMgmtServerLogic::SetPanMax(int16_t aPanMax) |
| { |
| VerifyOrReturnError(HasFeature(Feature::kMechanicalPan), CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute)); |
| VerifyOrReturnError((aPanMax <= kPanMaxMaxValue) && (aPanMax >= kPanMaxMinValue), CHIP_IM_GLOBAL_STATUS(ConstraintError)); |
| |
| if (aPanMax != mPanMax) |
| { |
| mPanMax = aPanMax; |
| MarkDirty(Attributes::PanMax::Id); |
| } |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR CameraAvSettingsUserLevelMgmtServerLogic::SetZoomMax(uint8_t aZoomMax) |
| { |
| VerifyOrReturnError(HasFeature(Feature::kMechanicalZoom), CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute)); |
| VerifyOrReturnError((aZoomMax <= kZoomMaxMaxValue) && (aZoomMax >= kZoomMaxMinValue), CHIP_IM_GLOBAL_STATUS(ConstraintError)); |
| |
| if (aZoomMax != mZoomMax) |
| { |
| mZoomMax = aZoomMax; |
| MarkDirty(Attributes::ZoomMax::Id); |
| } |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| /** |
| * Mutators for server copies of Pan, Tilt, and Zoom that may be invoked by a delegate or the server itself in responding to command |
| * callbacks, or due to local on device changes. |
| * Only set the value if the Feature Flag is set. |
| * It is entirely possible for a mutator to be called with a parameter that has no value. Case in point an invoke of |
| * MPTZSetPosition, this will be handled and the attributes updated if at least one of the three pan, tilt, or zoom have a value, |
| * with all three params passed through once validation is complete. An empty value is just ignored. |
| */ |
| void CameraAvSettingsUserLevelMgmtServerLogic::SetPan(Optional<int16_t> aPan) |
| { |
| if (HasFeature(Feature::kMechanicalPan)) |
| { |
| if (aPan.HasValue()) |
| { |
| mMptzPosition.pan = aPan; |
| LogErrorOnFailure(StoreMPTZPosition(mMptzPosition)); |
| MarkDirty(Attributes::MPTZPosition::Id); |
| } |
| } |
| } |
| |
| void CameraAvSettingsUserLevelMgmtServerLogic::SetTilt(Optional<int16_t> aTilt) |
| { |
| if (HasFeature(Feature::kMechanicalTilt)) |
| { |
| if (aTilt.HasValue()) |
| { |
| mMptzPosition.tilt = aTilt; |
| LogErrorOnFailure(StoreMPTZPosition(mMptzPosition)); |
| MarkDirty(Attributes::MPTZPosition::Id); |
| } |
| } |
| } |
| |
| void CameraAvSettingsUserLevelMgmtServerLogic::SetZoom(Optional<uint8_t> aZoom) |
| { |
| if (HasFeature(Feature::kMechanicalZoom)) |
| { |
| if (aZoom.HasValue()) |
| { |
| mMptzPosition.zoom = aZoom; |
| LogErrorOnFailure(StoreMPTZPosition(mMptzPosition)); |
| MarkDirty(Attributes::MPTZPosition::Id); |
| } |
| } |
| } |
| |
| void CameraAvSettingsUserLevelMgmtServerLogic::SetMovementState(PhysicalMovementEnum aMovementState) |
| { |
| if (HasFeature(Feature::kMechanicalPan) || HasFeature(Feature::kMechanicalTilt) || HasFeature(Feature::kMechanicalZoom)) |
| { |
| // The attribute is only ever changed to a new value, so this will always be dirty |
| mMovementState = aMovementState; |
| MarkDirty(Attributes::MovementState::Id); |
| } |
| } |
| |
| /** |
| * Methods handling known video stream IDs, the addition and removal thereof. |
| */ |
| void CameraAvSettingsUserLevelMgmtServerLogic::AddMoveCapableVideoStream(uint16_t aVideoStreamID, |
| Globals::Structs::ViewportStruct::Type aViewport) |
| { |
| CameraAvSettingsUserLevelManagement::Structs::DPTZStruct::Type dptzEntry; |
| dptzEntry.videoStreamID = aVideoStreamID; |
| dptzEntry.viewport = aViewport; |
| mDptzStreams.push_back(dptzEntry); |
| |
| CHIP_ERROR err = StoreDPTZStreams(); |
| if (err != CHIP_NO_ERROR) |
| { |
| ChipLogError(Zcl, |
| "CameraAVSettingsUserLevelMgmt[wp=%d]: Failed to persist newly created DPTZStream entry on stream allocation, " |
| "rolling back.", |
| mEndpointId); |
| mDptzStreams.pop_back(); |
| return; |
| } |
| |
| MarkDirty(Attributes::DPTZStreams::Id); |
| } |
| |
| void CameraAvSettingsUserLevelMgmtServerLogic::UpdateMoveCapableVideoStream(uint16_t aVideoStreamID, |
| Globals::Structs::ViewportStruct::Type aViewport) |
| { |
| auto it = std::find_if(mDptzStreams.begin(), mDptzStreams.end(), |
| [aVideoStreamID](const CameraAvSettingsUserLevelManagement::Structs::DPTZStruct::Type & dptzs) { |
| return dptzs.videoStreamID == aVideoStreamID; |
| }); |
| |
| if (it == mDptzStreams.end()) |
| { |
| ChipLogError(Zcl, "CameraAVSettingsUserLevelMgmt[ep=%d]. No matching video stream ID, update not possible. ID=%d.", |
| mEndpointId, aVideoStreamID); |
| return; |
| } |
| |
| // Temporarily store the current value incase of persistence failure |
| Globals::Structs::ViewportStruct::Type currentViewport; |
| currentViewport = it->viewport; |
| it->viewport = aViewport; |
| |
| CHIP_ERROR err = StoreDPTZStreams(); |
| if (err != CHIP_NO_ERROR) |
| { |
| ChipLogError( |
| Zcl, |
| "CameraAVSettingsUserLevelMgmt[wp=%d]: Failed to persist updated DPTZStream entry on viewport update, rolling back.", |
| mEndpointId); |
| it->viewport = currentViewport; |
| return; |
| } |
| |
| MarkDirty(Attributes::DPTZStreams::Id); |
| } |
| |
| void CameraAvSettingsUserLevelMgmtServerLogic::UpdateMoveCapableVideoStreams(Globals::Structs::ViewportStruct::Type aViewport) |
| { |
| if (mDptzStreams.empty()) |
| { |
| ChipLogError(Zcl, |
| "CameraAVSettingsUserLevelMgmt[wp=%d]: Attempt to update video streams when none available in DPTZStreams.", |
| mEndpointId); |
| return; |
| } |
| |
| // Temporarily copy the existing streams incase we need to roll back the update |
| std::vector<CameraAvSettingsUserLevelManagement::Structs::DPTZStruct::Type> tempDptzStreams; |
| tempDptzStreams = mDptzStreams; |
| |
| for (auto & dptzStream : mDptzStreams) |
| { |
| dptzStream.viewport = aViewport; |
| } |
| |
| CHIP_ERROR err = StoreDPTZStreams(); |
| if (err != CHIP_NO_ERROR) |
| { |
| ChipLogError(Zcl, |
| "CameraAVSettingsUserLevelMgmt[wp=%d]: Failed to persist updated DPTZStreams entry on global viewport update, " |
| "rolling back.", |
| mEndpointId); |
| mDptzStreams = tempDptzStreams; |
| return; |
| } |
| |
| MarkDirty(Attributes::DPTZStreams::Id); |
| } |
| |
| void CameraAvSettingsUserLevelMgmtServerLogic::RemoveMoveCapableVideoStream(uint16_t aVideoStreamID) |
| { |
| // Verify that this is a known ID, if it is, remove from the list |
| // |
| auto it = std::find_if(mDptzStreams.begin(), mDptzStreams.end(), |
| [aVideoStreamID](const CameraAvSettingsUserLevelManagement::Structs::DPTZStruct::Type & dptzs) { |
| return dptzs.videoStreamID == aVideoStreamID; |
| }); |
| |
| if (it == mDptzStreams.end()) |
| { |
| ChipLogError(Zcl, "CameraAVSettingsUserLevelMgmt[ep=%d]. No matching video stream ID, removal not possible. ID=%d.", |
| mEndpointId, aVideoStreamID); |
| return; |
| } |
| |
| // Allow for removal from memory even if removal from persistence storage fails. Otherwise clients could continue to |
| // attempt DPTZ modifications on a stream that has been de-allocated. |
| // |
| mDptzStreams.erase(it); |
| LogErrorOnFailure(StoreDPTZStreams()); |
| MarkDirty(Attributes::DPTZStreams::Id); |
| } |
| |
| /** |
| * @returns bool True if the provided video stream ID is known to the server. False if not. |
| */ |
| bool CameraAvSettingsUserLevelMgmtServerLogic::KnownVideoStreamID(uint16_t aVideoStreamID) |
| { |
| auto it = std::find_if(mDptzStreams.begin(), mDptzStreams.end(), |
| [aVideoStreamID](const DPTZStruct::Type & dptzs) { return dptzs.videoStreamID == aVideoStreamID; }); |
| |
| return (it == mDptzStreams.end() ? false : true); |
| } |
| |
| /** |
| * Helper function for setting the next preset ID to use in advance of reception of an MPTZSavePreset. |
| * The method loops over the range of possible IDs, starting with the current ID, if the preset ID is in use, |
| * it continues to the next possible value, looping back to 1. The checking is needed as the preset IDs aren't |
| * solely server generated, they can also be provided by a client. |
| * If there are no free presets (which will happen if all slots are taken), the value is not updated. |
| */ |
| void CameraAvSettingsUserLevelMgmtServerLogic::UpdatePresetID() |
| { |
| uint8_t nextIDToCheck = mCurrentPresetID; |
| ChipLogDetail(Zcl, "CameraAVSettingsUserLevelMgmt[ep=%d]: UpdatePresetID. Current Preset is %d.", mEndpointId, |
| mCurrentPresetID); |
| |
| do |
| { |
| nextIDToCheck = static_cast<uint8_t>((nextIDToCheck % mMaxPresets) + 1); |
| |
| // Have we lapped back round to where we started? If so, break |
| // |
| if (nextIDToCheck == mCurrentPresetID) |
| { |
| break; |
| } |
| |
| auto it = std::find_if(mMptzPresetHelpers.begin(), mMptzPresetHelpers.end(), |
| [=](const CameraAvSettingsUserLevelManagement::MPTZPresetHelper & mptzph) { |
| return mptzph.GetPresetID() == nextIDToCheck; |
| }); |
| if (it == mMptzPresetHelpers.end()) |
| { |
| mCurrentPresetID = nextIDToCheck; |
| break; |
| } |
| } while (true); |
| |
| ChipLogDetail(Zcl, "CameraAVSettingsUserLevelMgmt[ep=%d]: Updated PresetID is %d.", mEndpointId, mCurrentPresetID); |
| } |
| |
| /** |
| * Helper Read functions for complex attribute types |
| */ |
| CHIP_ERROR CameraAvSettingsUserLevelMgmtServerLogic::ReadAndEncodeMPTZPresets(AttributeValueEncoder & aEncoder) |
| { |
| return aEncoder.EncodeList([this](const auto & encoder) -> CHIP_ERROR { |
| for (const auto & mptzPresets : mMptzPresetHelpers) |
| { |
| // Get the details to encode from the preset helper |
| // |
| MPTZPresetStruct::Type presetStruct; |
| std::string name = mptzPresets.GetName(); |
| uint8_t preset = mptzPresets.GetPresetID(); |
| presetStruct.presetID = preset; |
| presetStruct.name = CharSpan(name.c_str(), name.size()); |
| presetStruct.settings = mptzPresets.GetMptzPosition(); |
| ChipLogDetail(Zcl, "CameraAVSettingsUserLevelMgmt[ep=%d]: Encoding an instance of MPTZPresetStruct. ID = %d. Name = %s", |
| mEndpointId, presetStruct.presetID, NullTerminated(presetStruct.name).c_str()); |
| ReturnErrorOnFailure(encoder.Encode(presetStruct)); |
| } |
| |
| return CHIP_NO_ERROR; |
| }); |
| } |
| |
| CHIP_ERROR CameraAvSettingsUserLevelMgmtServerLogic::ReadAndEncodeDPTZStreams(AttributeValueEncoder & aEncoder) |
| { |
| return aEncoder.EncodeList([this](const auto & encoder) -> CHIP_ERROR { |
| for (const auto & dptzStream : mDptzStreams) |
| { |
| ReturnErrorOnFailure(encoder.Encode(dptzStream)); |
| } |
| |
| return CHIP_NO_ERROR; |
| }); |
| } |
| |
| void CameraAvSettingsUserLevelMgmtServerLogic::LoadPersistentAttributes() |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| // Load MPTZPosition |
| MPTZStruct::Type storedMPTZPosition; |
| err = LoadMPTZPosition(storedMPTZPosition); |
| if (err == CHIP_NO_ERROR) |
| { |
| mMptzPosition = storedMPTZPosition; |
| ChipLogDetail(Zcl, "CameraAVSettingsUserLevelMgmt[ep=%d]: Loaded MPTZPosition", mEndpointId); |
| } |
| else |
| { |
| ChipLogDetail(Zcl, "CameraAVSettingsUserLevelMgmt[ep=%d]: Unable to load the MPTZPosition from the KVS.", mEndpointId); |
| |
| // No stored value, set defaults |
| SetPan(MakeOptional(kDefaultPan)); |
| SetTilt(MakeOptional(kDefaultTilt)); |
| SetZoom(MakeOptional(kDefaultZoom)); |
| } |
| |
| // Load MPTZPresets |
| err = LoadMPTZPresets(); |
| if (err != CHIP_NO_ERROR) |
| { |
| ChipLogDetail(Zcl, "CameraAVSettingsUserLevelMgmt[ep=%d]: Unable to load the MPTZPresets from the KVS.", mEndpointId); |
| |
| // Clear the local storage |
| mMptzPresetHelpers.clear(); |
| } |
| else |
| { |
| ChipLogDetail(Zcl, "CameraAVSettingsUserLevelMgmt[ep=%d]: Loaded MPTZPresets", mEndpointId); |
| } |
| |
| // Load DPTZRelativeMove |
| err = LoadDPTZStreams(); |
| if (err != CHIP_NO_ERROR) |
| { |
| ChipLogDetail(Zcl, "CameraAVSettingsUserLevelMgmt[ep=%d]: Unable to load the DPTZRelativeMove from the KVS.", mEndpointId); |
| |
| // Clear the local storage |
| mDptzStreams.clear(); |
| } |
| else |
| { |
| ChipLogDetail(Zcl, "CameraAVSettingsUserLevelMgmt[ep=%d]: Loaded DPTZStreams", mEndpointId); |
| } |
| |
| // Signal delegate that all persistent configuration attributes have been loaded. |
| TEMPORARY_RETURN_IGNORED mDelegate->PersistentAttributesLoadedCallback(); |
| } |
| |
| std::optional<DataModel::ActionReturnStatus> CameraAvSettingsUserLevelMgmtServerLogic::HandleMPTZSetPosition( |
| CommandHandler & handler, const ConcreteCommandPath & commandPath, |
| const CameraAvSettingsUserLevelManagement::Commands::MPTZSetPosition::DecodableType & commandData) |
| { |
| bool hasAtLeastOneValue = false; |
| |
| Optional<int16_t> pan = commandData.pan; |
| Optional<int16_t> tilt = commandData.tilt; |
| Optional<uint8_t> zoom = commandData.zoom; |
| |
| // Validate the received command fields |
| // |
| if (pan.HasValue()) |
| { |
| VerifyOrReturnError( |
| HasFeature(Feature::kMechanicalPan), Status::InvalidCommand, |
| ChipLogError(Zcl, "CameraAVSettingsUserLevelMgmt[ep=%d]: Mechanical Pan not supported although pan value provided", |
| mEndpointId)); |
| |
| int16_t panValue = pan.Value(); |
| VerifyOrReturnError( |
| (panValue <= mPanMax) && (panValue >= mPanMin), Status::ConstraintError, |
| ChipLogError(Zcl, "CameraAVSettingsUserLevelMgmt[ep=%d]: Received Pan value out of range", mEndpointId)); |
| |
| hasAtLeastOneValue = true; |
| } |
| |
| if (tilt.HasValue()) |
| { |
| VerifyOrReturnError( |
| HasFeature(Feature::kMechanicalTilt), Status::InvalidCommand, |
| ChipLogError(Zcl, "CameraAVSettingsUserLevelMgmt[ep=%d]: Mechanical Tilt not supported although tilt value provided", |
| mEndpointId)); |
| |
| int16_t tiltValue = tilt.Value(); |
| VerifyOrReturnError( |
| (tiltValue <= mTiltMax) && (tiltValue >= mTiltMin), Status::ConstraintError, |
| ChipLogError(Zcl, "CameraAVSettingsUserLevelMgmt[ep=%d]: Received Tilt value out of range", mEndpointId)); |
| |
| hasAtLeastOneValue = true; |
| } |
| |
| if (zoom.HasValue()) |
| { |
| VerifyOrReturnError( |
| HasFeature(Feature::kMechanicalZoom), Status::InvalidCommand, |
| ChipLogError(Zcl, "CameraAVSettingsUserLevelMgmt[ep=%d]: Mechanical Zoom not supported although zoom value provided", |
| mEndpointId)); |
| |
| uint8_t zoomValue = zoom.Value(); |
| |
| VerifyOrReturnError( |
| (zoomValue <= mZoomMax) && (zoomValue >= kZoomMinValue), Status::ConstraintError, |
| ChipLogError(Zcl, "CameraAVSettingsUserLevelMgmt[ep=%d]: Received Zoom value out of range", mEndpointId)); |
| |
| hasAtLeastOneValue = true; |
| } |
| |
| // Was a value received in the command |
| VerifyOrReturnError(hasAtLeastOneValue, Status::InvalidCommand, |
| ChipLogError(Zcl, |
| "CameraAVSettingsUserLevelMgmt[ep=%d]: MPTZSetPosition command contains no actionable fields", |
| mEndpointId)); |
| |
| // If the camera is still in motion, then return Busy. Subsequently check with the delegate that we're in a position to change |
| // any of the PTZ values which may not be possible for other reasons |
| // |
| VerifyOrReturnError( |
| !IsMoving(), Status::Busy, |
| ChipLogError(Zcl, "CameraAVSettingsUserLevelMgmt[ep=%d]: cannot execute command as camera is busy with a physical movement", |
| mEndpointId)); |
| |
| VerifyOrReturnError( |
| mDelegate->CanChangeMPTZ(), Status::Busy, |
| ChipLogDetail(Zcl, "CameraAVSettingsUserLevelMgmt[ep=%d]: Device not able to process MPTZ change", mEndpointId)); |
| |
| Status status = mDelegate->MPTZSetPosition(pan, tilt, zoom, this); |
| |
| if (status != Status::Success) |
| { |
| return status; |
| } |
| |
| // We don't update the server persisted attributes until the delegate confirms via callback that the physical device movement is |
| // complete |
| // |
| mTargetPan = pan; |
| mTargetTilt = tilt; |
| mTargetZoom = zoom; |
| |
| SetMovementState(PhysicalMovementEnum::kMoving); |
| |
| return status; |
| } |
| |
| std::optional<DataModel::ActionReturnStatus> CameraAvSettingsUserLevelMgmtServerLogic::HandleMPTZRelativeMove( |
| CommandHandler & handler, const ConcreteCommandPath & commandPath, |
| const CameraAvSettingsUserLevelManagement::Commands::MPTZRelativeMove::DecodableType & commandData) |
| { |
| bool hasAtLeastOneValue = false; |
| |
| Optional<int16_t> panDelta = commandData.panDelta; |
| Optional<int16_t> tiltDelta = commandData.tiltDelta; |
| Optional<int8_t> zoomDelta = commandData.zoomDelta; |
| |
| // These will all be set to actual values if there is a delta provided, and constraint checking passes. |
| Optional<int16_t> newPan; |
| Optional<int16_t> newTilt; |
| Optional<uint8_t> newZoom; |
| |
| // Validate the received command fields |
| // |
| if (panDelta.HasValue()) |
| { |
| VerifyOrReturnError( |
| HasFeature(Feature::kMechanicalPan), Status::InvalidCommand, |
| ChipLogError(Zcl, "CameraAVSettingsUserLevelMgmt[ep=%d]: Mechanical Pan not supported although panDelta value provided", |
| mEndpointId)); |
| |
| int16_t panDeltaValue = panDelta.Value(); |
| VerifyOrReturnError( |
| (panDeltaValue <= (mPanMax - mPanMin) && panDeltaValue >= -(mPanMax - mPanMin)), Status::ConstraintError, |
| ChipLogError(Zcl, "CameraAVSettingsUserLevelMgmt[ep=%d]: PanDelta value received is out of range.", mEndpointId)); |
| |
| // If we're here, then we'll also have an existing Pan value in MPTZPosition |
| // Note that newPan is always between -180 and 180; and the panDeltaValue is constrained to -360 to 360 by the |
| // code above, so the addition sum can never overflow int16_t. |
| // |
| int16_t newPanValue = static_cast<int16_t>(mMptzPosition.pan.Value() + panDeltaValue); |
| if (newPanValue > mPanMax) |
| { |
| newPanValue = mPanMax; |
| } |
| if (newPanValue < mPanMin) |
| { |
| newPanValue = mPanMin; |
| } |
| |
| newPan.Emplace(newPanValue); |
| |
| hasAtLeastOneValue = true; |
| } |
| |
| if (tiltDelta.HasValue()) |
| { |
| VerifyOrReturnError( |
| HasFeature(Feature::kMechanicalTilt), Status::InvalidCommand, |
| ChipLogError(Zcl, |
| "CameraAVSettingsUserLevelMgmt[ep=%d]: Mechanical Tilt not supported although tiltDelta value provided", |
| mEndpointId)); |
| |
| int16_t tiltDeltaValue = tiltDelta.Value(); |
| VerifyOrReturnError( |
| (tiltDeltaValue <= (mTiltMax - mTiltMin) && tiltDeltaValue >= -(mTiltMax - mTiltMin)), Status::ConstraintError, |
| ChipLogError(Zcl, "CameraAVSettingsUserLevelMgmt[ep=%d]: TiltDelta value received is out of range.", mEndpointId)); |
| |
| // If we're here, then we'll also have an existing Tilt value in MPTZPosition |
| // Note that newTilt is always between -180 and 180; and the tiltDeltaValue is constrained to -360 to 360 by the |
| // code above, so the addition sum can never overflow int16_t. |
| // |
| int16_t newTiltValue = static_cast<int16_t>(mMptzPosition.tilt.Value() + tiltDeltaValue); |
| if (newTiltValue > mTiltMax) |
| { |
| newTiltValue = mTiltMax; |
| } |
| if (newTiltValue < mTiltMin) |
| { |
| newTiltValue = mTiltMin; |
| } |
| |
| newTilt.Emplace(newTiltValue); |
| |
| hasAtLeastOneValue = true; |
| } |
| |
| if (zoomDelta.HasValue()) |
| { |
| VerifyOrReturnError( |
| HasFeature(Feature::kMechanicalZoom), Status::InvalidCommand, |
| ChipLogError(Zcl, |
| "CameraAVSettingsUserLevelMgmt[ep=%d]: Mechanical Zoom not supported although zoomDelta value provided", |
| mEndpointId)); |
| |
| int8_t zoomDeltaValue = zoomDelta.Value(); |
| VerifyOrReturnError( |
| (zoomDeltaValue <= (mZoomMax - 1) && zoomDeltaValue >= -(mZoomMax - 1)), Status::ConstraintError, |
| ChipLogError(Zcl, "CameraAVSettingsUserLevelMgmt[ep=%d]: ZoomDelta value received is out of range.", mEndpointId)); |
| |
| // If we're here, then we'll also have an existing Zoom value in MPTZPosition. The zoom values are constrained such that |
| // we won't overflow newZoomValue |
| // |
| int newZoomValue = static_cast<int>(mMptzPosition.zoom.Value()) + zoomDeltaValue; |
| |
| if (newZoomValue > mZoomMax) |
| { |
| newZoomValue = static_cast<int>(mZoomMax); |
| } |
| if (newZoomValue < 1) |
| { |
| newZoomValue = 1; |
| } |
| |
| ChipLogProgress(Zcl, "New Zoom Value %d", newZoomValue); |
| |
| newZoom.Emplace(static_cast<uint8_t>(newZoomValue)); |
| |
| hasAtLeastOneValue = true; |
| } |
| |
| // Was a value received in the command |
| VerifyOrReturnError(hasAtLeastOneValue, Status::InvalidCommand, |
| ChipLogError(Zcl, |
| "CameraAVSettingsUserLevelMgmt[ep=%d]: MPTZRelativeMove command contains no actionable fields", |
| mEndpointId)); |
| |
| // If the camera is still in motion, then return Busy. Subsequently check with the delegate that we're in a position to change |
| // any of the PTZ values which may not be possible for other reasons |
| // |
| VerifyOrReturnError( |
| !IsMoving(), Status::Busy, |
| ChipLogError(Zcl, "CameraAVSettingsUserLevelMgmt[ep=%d]: cannot execute command as camera is busy with a physical movement", |
| mEndpointId)); |
| |
| VerifyOrReturnError(mDelegate->CanChangeMPTZ(), Status::Busy, |
| ChipLogDetail(Zcl, |
| "CameraAVSettingsUserLevelMgmt[ep=%d]: Device not able to process MPTZ relative value change", |
| mEndpointId)); |
| |
| Status status = mDelegate->MPTZRelativeMove(newPan, newTilt, newZoom, this); |
| |
| if (status != Status::Success) |
| { |
| return status; |
| } |
| |
| // We don't update the server persisted attributes until the delegate confirms via callback that the physical device movement is |
| // complete |
| // |
| mTargetPan = newPan; |
| mTargetTilt = newTilt; |
| mTargetZoom = newZoom; |
| SetMovementState(PhysicalMovementEnum::kMoving); |
| |
| return status; |
| } |
| |
| std::optional<DataModel::ActionReturnStatus> CameraAvSettingsUserLevelMgmtServerLogic::HandleMPTZMoveToPreset( |
| CommandHandler & handler, const ConcreteCommandPath & commandPath, |
| const CameraAvSettingsUserLevelManagement::Commands::MPTZMoveToPreset::DecodableType & commandData) |
| { |
| uint8_t preset = commandData.presetID; |
| |
| // Verify the provided presetID is within spec limits |
| // |
| VerifyOrReturnError((preset <= mMaxPresets) && (preset >= 1), Status::ConstraintError, |
| ChipLogError(Zcl, "CameraAVSettingsUserLevelMgmt[ep=%d]: Preset provided is out of range. Preset: %d", |
| mEndpointId, preset)); |
| |
| // Do we have any presets? |
| // |
| VerifyOrReturnError( |
| !mMptzPresetHelpers.empty(), Status::NotFound, |
| ChipLogError(Zcl, "CameraAVSettingsUserLevelMgmt[ep=%d]: No stored presets, MoveToPreset not possible", mEndpointId)); |
| |
| // We have presets, check that the received ID is a valid preset ID |
| // |
| auto it = std::find_if( |
| mMptzPresetHelpers.begin(), mMptzPresetHelpers.end(), |
| [preset](const CameraAvSettingsUserLevelManagement::MPTZPresetHelper & mptzph) { return mptzph.GetPresetID() == preset; }); |
| |
| VerifyOrReturnError( |
| it != mMptzPresetHelpers.end(), Status::NotFound, |
| ChipLogError(Zcl, |
| "CameraAVSettingsUserLevelMgmt[ep=%d]: No matching presets, MoveToPreset not possible for provided preset: %d", |
| mEndpointId, preset)); |
| |
| // If the camera is still in motion, then return Busy. Subsequently check with the delegate that we're in a position to change |
| // any of the PTZ values which may not be possible for other reasons |
| // |
| VerifyOrReturnError( |
| !IsMoving(), Status::Busy, |
| ChipLogError(Zcl, "CameraAVSettingsUserLevelMgmt[ep=%d]: cannot execute command as camera is busy with a physical movement", |
| mEndpointId)); |
| |
| VerifyOrReturnError( |
| mDelegate->CanChangeMPTZ(), Status::Busy, |
| ChipLogDetail(Zcl, "CameraAVSettingsUserLevelMgmt[ep=%d]: Device not able to process move to MPTZ preset", mEndpointId)); |
| |
| auto presetValues = it->GetMptzPosition(); |
| |
| // Inform the delegate that the device is requested to move to PTZ values given by the selected preset id |
| Status status = mDelegate->MPTZMoveToPreset(preset, presetValues.pan, presetValues.tilt, presetValues.zoom, this); |
| |
| if (status != Status::Success) |
| { |
| return status; |
| } |
| |
| // We don't update the server persisted attributes until the delegate confirms via callback that the physical device movement is |
| // complete |
| // |
| mTargetPan = presetValues.pan; |
| mTargetTilt = presetValues.tilt; |
| mTargetZoom = presetValues.zoom; |
| SetMovementState(PhysicalMovementEnum::kMoving); |
| |
| return status; |
| } |
| |
| std::optional<DataModel::ActionReturnStatus> CameraAvSettingsUserLevelMgmtServerLogic::HandleMPTZSavePreset( |
| CommandHandler & handler, const ConcreteCommandPath & commandPath, |
| const CameraAvSettingsUserLevelManagement::Commands::MPTZSavePreset::DecodableType & commandData) |
| { |
| Status status = Status::Success; |
| |
| Optional<uint8_t> preset = commandData.presetID; |
| chip::CharSpan presetName = commandData.name; |
| uint8_t presetToUse = mCurrentPresetID; |
| |
| // Do we have a user provided preset ID? If yes, is it in range? |
| // |
| if (preset.HasValue()) |
| { |
| VerifyOrReturnError((preset.Value() <= mMaxPresets) && (preset.Value() >= 1), Status::ConstraintError, |
| ChipLogError(Zcl, |
| "CameraAVSettingsUserLevelMgmt[ep=%d]: Provided preset ID is out of range. Preset: %d", |
| mEndpointId, preset.Value())); |
| presetToUse = preset.Value(); |
| } |
| |
| // Does the preset equate to an already known stored preset? If so we're updating that one rather than creating a new one |
| // |
| auto it = std::find_if(mMptzPresetHelpers.begin(), mMptzPresetHelpers.end(), |
| [presetToUse](const CameraAvSettingsUserLevelManagement::MPTZPresetHelper & mptzph) { |
| return mptzph.GetPresetID() == presetToUse; |
| }); |
| |
| // If the current preset ID results in an entry from the current known set and there was a provided preset then we're updating |
| // an existing preset. Only check for exhausting max presets if we're NOT updating. |
| // It is possible that mCurrentPresetID equates to a current preset in cases where the collection of presets is full |
| // |
| bool updatingExistingPreset = (it != mMptzPresetHelpers.end()) && (preset.HasValue()); |
| |
| if (!updatingExistingPreset) |
| { |
| // Make sure that the vector will not exceed the max size |
| // |
| VerifyOrReturnError( |
| mMptzPresetHelpers.size() != mMaxPresets, Status::ResourceExhausted, |
| ChipLogError(Zcl, |
| "CameraAVSettingsUserLevelMgmt[ep=%d]: No more space for additional presets, MPTZSavePreset not possible", |
| mEndpointId)); |
| } |
| |
| // Call the delegate, make sure that it is ok to save a new preset, given the current |
| // delegate aware values for MPTZ |
| // |
| status = mDelegate->MPTZSavePreset(presetToUse); |
| |
| if (status != Status::Success) |
| { |
| return status; |
| } |
| |
| // Capture the current MPTZ values in the preset |
| // |
| CameraAvSettingsUserLevelManagement::MPTZPresetHelper aMptzPresetHelper; |
| |
| aMptzPresetHelper.SetPresetID(presetToUse); |
| aMptzPresetHelper.SetName(presetName); |
| aMptzPresetHelper.SetMptzPosition(mMptzPosition); |
| |
| // If an update, replace what is at the iterator, otherwise add to the set as this is new. Store the current value incase |
| // we need to roll it back. |
| // |
| CameraAvSettingsUserLevelManagement::MPTZPresetHelper currentMptzPresetHelper; |
| |
| if (updatingExistingPreset) |
| { |
| currentMptzPresetHelper = *it; |
| ChipLogDetail(Zcl, "CameraAVSettingsUserLevelMgmt[ep=%d]: Updating existing MPTZ Preset. Preset ID = %d. Preset Name = %s", |
| mEndpointId, presetToUse, aMptzPresetHelper.GetName().c_str()); |
| *it = aMptzPresetHelper; |
| } |
| else |
| { |
| ChipLogDetail(Zcl, "CameraAVSettingsUserLevelMgmt[ep=%d]: Saving new MPTZ Preset. Preset ID = %d. Preset Name = %s", |
| mEndpointId, presetToUse, aMptzPresetHelper.GetName().c_str()); |
| mMptzPresetHelpers.push_back(aMptzPresetHelper); |
| } |
| |
| // Try to persist the value first, if that fails we fail the command and roll-back the update to our stored presets |
| // |
| CHIP_ERROR err = StoreMPTZPresets(); |
| |
| if (err != CHIP_NO_ERROR) |
| { |
| if (updatingExistingPreset) |
| { |
| *it = currentMptzPresetHelper; |
| } |
| else |
| { |
| mMptzPresetHelpers.pop_back(); |
| } |
| ChipLogError(Zcl, "CameraAVSettingsUserLevelMgmt[ep=%d]: Failed to persist new value for MPTZPresets", mEndpointId); |
| return Status::Failure; |
| } |
| |
| // Update the current preset ID to the next available only if we actually used the current value. A user provided preset |
| // could have any value between 1 and MaxPresets |
| // |
| if (presetToUse == mCurrentPresetID) |
| { |
| UpdatePresetID(); |
| } |
| |
| MarkDirty(Attributes::MPTZPresets::Id); |
| |
| return Status::Success; |
| } |
| |
| std::optional<DataModel::ActionReturnStatus> CameraAvSettingsUserLevelMgmtServerLogic::HandleMPTZRemovePreset( |
| CommandHandler & handler, const ConcreteCommandPath & commandPath, |
| const CameraAvSettingsUserLevelManagement::Commands::MPTZRemovePreset::DecodableType & commandData) |
| { |
| uint8_t presetToRemove = commandData.presetID; |
| |
| // Verify the provided presetID is within spec limits |
| // |
| VerifyOrReturnError((presetToRemove <= mMaxPresets) && (presetToRemove >= 1), Status::ConstraintError, |
| ChipLogError(Zcl, "CameraAVSettingsUserLevelMgmt[ep=%d]: Preset to remove is out of range. Preset: %d", |
| mEndpointId, presetToRemove)); |
| |
| // Is the provided ID known to us? |
| // |
| auto it = std::find_if(mMptzPresetHelpers.begin(), mMptzPresetHelpers.end(), |
| [presetToRemove](const CameraAvSettingsUserLevelManagement::MPTZPresetHelper & mptzph) { |
| return mptzph.GetPresetID() == presetToRemove; |
| }); |
| |
| VerifyOrReturnError( |
| it != mMptzPresetHelpers.end(), Status::NotFound, |
| ChipLogError(Zcl, |
| "CameraAVSettingsUserLevelMgmt[ep=%d]: No matching presets, RemovePreset not possible for provided preset: %d", |
| mEndpointId, presetToRemove)); |
| |
| // Call the delegate to ensure that it is ok to remove the preset indicated. |
| // |
| Status status = mDelegate->MPTZRemovePreset(presetToRemove); |
| |
| if (status != Status::Success) |
| { |
| return status; |
| } |
| |
| // Try to persist the updated value first, if that fails we fail the command otherwise the data would be out of sync between |
| // in memory and persisted. |
| |
| CHIP_ERROR err = StoreMPTZPresets(); |
| |
| if (err != CHIP_NO_ERROR) |
| { |
| ChipLogError(Zcl, "CameraAVSettingsUserLevelMgmt[ep=%d]: Failed to persist removal of preset from MPTZPresets", |
| mEndpointId); |
| return Status::Failure; |
| } |
| |
| // Remove the identified item from the known set of presets |
| // |
| mMptzPresetHelpers.erase(it); |
| MarkDirty(Attributes::MPTZPresets::Id); |
| |
| return Status::Success; |
| } |
| |
| std::optional<DataModel::ActionReturnStatus> CameraAvSettingsUserLevelMgmtServerLogic::HandleDPTZSetViewport( |
| CommandHandler & handler, const ConcreteCommandPath & commandPath, |
| const CameraAvSettingsUserLevelManagement::Commands::DPTZSetViewport::DecodableType & commandData) |
| { |
| uint16_t videoStreamID = commandData.videoStreamID; |
| Globals::Structs::ViewportStruct::Type viewport = commandData.viewport; |
| |
| // Is this a video stream ID of which we have already been informed? |
| // If not, fail. |
| // |
| VerifyOrReturnError(KnownVideoStreamID(videoStreamID), Status::NotFound, |
| ChipLogError(Zcl, "CameraAVSettingsUserLevelMgmt[ep=%d]: Unknown Video Stream ID provided. ID: %d", |
| mEndpointId, videoStreamID)); |
| |
| // Call the delegate |
| Status status = mDelegate->DPTZSetViewport(videoStreamID, viewport); |
| |
| if (status == Status::Success) |
| { |
| // Update the viewport of our stream in DPTZStreams |
| // |
| UpdateMoveCapableVideoStream(videoStreamID, viewport); |
| } |
| |
| return status; |
| } |
| |
| std::optional<DataModel::ActionReturnStatus> CameraAvSettingsUserLevelMgmtServerLogic::HandleDPTZRelativeMove( |
| CommandHandler & handler, const ConcreteCommandPath & commandPath, |
| const CameraAvSettingsUserLevelManagement::Commands::DPTZRelativeMove::DecodableType & commandData) |
| { |
| uint16_t videoStreamID = commandData.videoStreamID; |
| Optional<int16_t> deltaX = commandData.deltaX; |
| Optional<int16_t> deltaY = commandData.deltaY; |
| Optional<int8_t> zoomDelta = commandData.zoomDelta; |
| |
| // Verify that a received Zoom Delta is within constraints |
| // |
| if (zoomDelta.HasValue()) |
| { |
| int8_t zoomDeltaValue = zoomDelta.Value(); |
| VerifyOrReturnError( |
| zoomDeltaValue >= -100 && zoomDeltaValue <= 100, Status::ConstraintError, |
| ChipLogError(Zcl, |
| "CameraAVSettingsUserLevelMgmt[ep=%d]: Provided Digital Zoom Delta is out of range. Provided Zoom: %d", |
| mEndpointId, zoomDeltaValue)); |
| } |
| // Is this a video stream ID of which we have already been informed via DPTZSetViewport. We can't relative move on a |
| // viewport that hasn't already been set, hence we fail if the provided id is not found. |
| // |
| VerifyOrReturnError(KnownVideoStreamID(videoStreamID), Status::NotFound, |
| ChipLogError(Zcl, "CameraAVSettingsUserLevelMgmt[ep=%d]. Unknown Video Stream ID provided. ID=%d.", |
| mEndpointId, videoStreamID)); |
| |
| // Create a viewport and call the delegate; on success update our Stream Viewport with that which was set |
| Globals::Structs::ViewportStruct::Type viewport; |
| Status status = mDelegate->DPTZRelativeMove(videoStreamID, deltaX, deltaY, zoomDelta, viewport); |
| |
| if (status == Status::Success) |
| { |
| UpdateMoveCapableVideoStream(videoStreamID, viewport); |
| } |
| |
| return status; |
| } |
| |
| // Physical device movement callback |
| // |
| void CameraAvSettingsUserLevelMgmtServerLogic::OnPhysicalMovementComplete(Status status) |
| { |
| // Make sure we're running in the Matter thread |
| assertChipStackLockedByCurrentThread(); |
| |
| if (status == Status::Success) |
| { |
| SetPan(mTargetPan); |
| SetTilt(mTargetTilt); |
| SetZoom(mTargetZoom); |
| } |
| else |
| { |
| ChipLogError( |
| Zcl, "CameraAVSettingsUserLevelMgmt[ep=%d]. Camera failed to move to new requested values of Pan, Tilt, and/or Zoom.", |
| mEndpointId); |
| } |
| |
| SetMovementState(PhysicalMovementEnum::kIdle); |
| } |
| |
| } // namespace Clusters |
| } // namespace app |
| } // namespace chip |