blob: 11daac10ece55ce0950af95735a5efb513d540c9 [file] [log] [blame]
/*
*
* Copyright (c) 2024 Project CHIP Authors
* All rights reserved.
*
* 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-common/zap-generated/attributes/Accessors.h>
#include <rvc-service-area-delegate.h>
#include <vector>
using namespace chip;
using namespace chip::app::Clusters;
using namespace chip::app::Clusters::ServiceArea;
void RvcServiceAreaDelegate::SetMapTopology()
{
ClearSupportedMaps();
GetInstance()->AddSupportedMap(supportedMapId_XX, "My Map XX"_span);
GetInstance()->AddSupportedMap(supportedMapId_YY, "My Map YY"_span);
// Area A has name, floor number, uses map XX
auto areaA =
AreaStructureWrapper{}
.SetAreaId(supportedAreaID_A)
.SetMapId(supportedMapId_XX)
.SetLocationInfo("My Location A"_span, DataModel::Nullable<int16_t>(4), DataModel::Nullable<Globals::AreaTypeTag>());
// Area B has name, uses map XX
auto areaB = AreaStructureWrapper{}
.SetAreaId(supportedAreaID_B)
.SetMapId(supportedMapId_XX)
.SetLocationInfo("My Location B"_span, DataModel::NullNullable, DataModel::NullNullable);
// Area C has full SemData, no name, Map YY
auto areaC = AreaStructureWrapper{}
.SetAreaId(supportedAreaID_C)
.SetMapId(supportedMapId_YY)
.SetLocationInfo(""_span, -1, Globals::AreaTypeTag::kPlayRoom)
.SetLandmarkInfo(Globals::LandmarkTag::kBackDoor, Globals::RelativePositionTag::kNextTo);
// Area D has null values for all landmark fields, Map YY
auto areaD = AreaStructureWrapper{}
.SetAreaId(supportedAreaID_D)
.SetMapId(supportedMapId_YY)
.SetLocationInfo("My Location D"_span, DataModel::NullNullable, DataModel::NullNullable)
.SetLandmarkInfo(Globals::LandmarkTag::kCouch, Globals::RelativePositionTag::kNextTo);
GetInstance()->AddSupportedArea(areaA);
GetInstance()->AddSupportedArea(areaB);
GetInstance()->AddSupportedArea(areaC);
GetInstance()->AddSupportedArea(areaD);
}
void RvcServiceAreaDelegate::SetNoMapTopology()
{
ClearSupportedMaps();
// Area A has name, floor number.
auto areaA =
AreaStructureWrapper{}
.SetAreaId(supportedAreaID_A)
.SetLocationInfo("My Location A"_span, DataModel::Nullable<int16_t>(4), DataModel::Nullable<Globals::AreaTypeTag>());
// Area B has name.
auto areaB = AreaStructureWrapper{}
.SetAreaId(supportedAreaID_B)
.SetLocationInfo("My Location B"_span, DataModel::NullNullable, DataModel::NullNullable);
// Area C has full SemData, no name.
auto areaC = AreaStructureWrapper{}
.SetAreaId(supportedAreaID_C)
.SetLocationInfo(""_span, -1, Globals::AreaTypeTag::kPlayRoom)
.SetLandmarkInfo(Globals::LandmarkTag::kBackDoor, Globals::RelativePositionTag::kNextTo);
// Area D has null values for all landmark fields.
auto areaD = AreaStructureWrapper{}
.SetAreaId(supportedAreaID_D)
.SetLocationInfo("My Location D"_span, DataModel::NullNullable, DataModel::NullNullable)
.SetLandmarkInfo(Globals::LandmarkTag::kCouch, Globals::RelativePositionTag::kNextTo);
GetInstance()->AddSupportedArea(areaA);
GetInstance()->AddSupportedArea(areaB);
GetInstance()->AddSupportedArea(areaC);
GetInstance()->AddSupportedArea(areaD);
}
CHIP_ERROR RvcServiceAreaDelegate::Init()
{
SetMapTopology();
GetInstance()->SetCurrentArea(supportedAreaID_C);
return CHIP_NO_ERROR;
}
//*************************************************************************
// command support
bool RvcServiceAreaDelegate::IsSetSelectedAreasAllowed(MutableCharSpan & statusText)
{
return (mIsSetSelectedAreasAllowedDeviceInstance->*mIsSetSelectedAreasAllowedCallback)(statusText);
};
bool RvcServiceAreaDelegate::IsValidSelectAreasSet(const Commands::SelectAreas::DecodableType & req, SelectAreasStatus & areaStatus,
MutableCharSpan & statusText)
{
// if req is empty list return true.
{
size_t reqSize;
if (req.newAreas.ComputeSize(&reqSize) != CHIP_NO_ERROR)
{
areaStatus = SelectAreasStatus::kInvalidSet; // todo Not sure this is the correct error to use here
CopyCharSpanToMutableCharSpan("error computing number of selected areas"_span, statusText);
return false;
}
if (reqSize == 0)
{
return true;
}
}
// If there are less than 2 supported maps, any combination of areas is valid.
if (!GetInstance()->HasFeature(Feature::kMaps) || GetNumberOfSupportedMaps() <= 1)
{
return true;
}
// Check that all the areas are in the same map.
auto newAreasIter = req.newAreas.begin();
newAreasIter.Next();
AreaStructureWrapper tempArea;
uint32_t ignoredIndex;
if (!GetSupportedAreaById(newAreasIter.GetValue(), ignoredIndex, tempArea))
{
areaStatus = SelectAreasStatus::kUnsupportedArea;
CopyCharSpanToMutableCharSpan("unable to find selected area in supported areas"_span, statusText);
return false;
}
auto mapId = tempArea.mapID.Value(); // It is safe to call `.Value()` as we confirmed that there are at least 2 maps.
while (newAreasIter.Next())
{
if (!GetSupportedAreaById(newAreasIter.GetValue(), ignoredIndex, tempArea))
{
areaStatus = SelectAreasStatus::kUnsupportedArea;
CopyCharSpanToMutableCharSpan("unable to find selected area in supported areas"_span, statusText);
return false;
}
if (tempArea.mapID.Value() != mapId)
{
areaStatus = SelectAreasStatus::kInvalidSet;
CopyCharSpanToMutableCharSpan("all selected areas must be in the same map"_span, statusText);
return false;
}
}
if (CHIP_NO_ERROR != newAreasIter.GetStatus())
{
areaStatus = SelectAreasStatus::kInvalidSet;
CopyCharSpanToMutableCharSpan("error processing new areas."_span, statusText);
return false;
}
return true;
};
bool RvcServiceAreaDelegate::HandleSkipCurrentArea(uint32_t skippedArea, MutableCharSpan & skipStatusText)
{
return (mHandleSkipCurrentAreaDeviceInstance->*mHandleSkipCurrentAreaCallback)(skippedArea, skipStatusText);
};
//*************************************************************************
// Supported Areas accessors
bool RvcServiceAreaDelegate::IsSupportedAreasChangeAllowed()
{
return (mIsSupportedAreasChangeAllowedDeviceInstance->*mIsSupportedAreasChangeAllowedCallback)();
}
uint32_t RvcServiceAreaDelegate::GetNumberOfSupportedAreas()
{
return static_cast<uint32_t>(mSupportedAreas.size());
}
bool RvcServiceAreaDelegate::GetSupportedAreaByIndex(uint32_t listIndex, AreaStructureWrapper & supportedArea)
{
if (listIndex < mSupportedAreas.size())
{
supportedArea = mSupportedAreas[listIndex];
return true;
}
return false;
};
bool RvcServiceAreaDelegate::GetSupportedAreaById(uint32_t aAreaID, uint32_t & listIndex, AreaStructureWrapper & supportedArea)
{
// We do not need to reimplement this method as it's already done by the SDK.
// We are reimplementing this method, still using linear search, but with some optimization on the SDK implementation
// since we have direct access to the list.
listIndex = 0;
while (listIndex < mSupportedAreas.size())
{
if (mSupportedAreas[listIndex].areaID == aAreaID)
{
supportedArea = mSupportedAreas[listIndex];
return true;
}
++listIndex;
}
return false;
};
bool RvcServiceAreaDelegate::AddSupportedArea(const AreaStructureWrapper & newArea, uint32_t & listIndex)
{
// The server instance (caller) is responsible for ensuring that there are no duplicate area IDs, list size not exceeded,
// etc.
// Double-check list size to ensure there no memory issues.
if (mSupportedAreas.size() < kMaxNumSupportedAreas)
{
// not sorting list, number of areas normally expected to be small, max 255
mSupportedAreas.push_back(newArea);
listIndex = static_cast<uint32_t>(mSupportedMaps.size()) - 1; // new element is last in list
return true;
}
ChipLogError(Zcl, "AddSupportedArea %u - supported areas list is already at maximum size %u", newArea.areaID,
static_cast<uint32_t>(kMaxNumSupportedAreas));
return false;
}
bool RvcServiceAreaDelegate::ModifySupportedArea(uint32_t listIndex, const AreaStructureWrapper & modifiedArea)
{
// The server instance (caller) is responsible for ensuring that there are no duplicate area IDs, list size not exceeded,
// etc.
// Double-check that areaID's match.
if (modifiedArea.areaID != mSupportedAreas[listIndex].areaID)
{
ChipLogError(Zcl, "ModifySupportedArea - areaID's do not match, new areaID %u, existing areaID %u", modifiedArea.areaID,
mSupportedAreas[listIndex].areaID);
return false;
}
// checks passed, update the attribute
mSupportedAreas[listIndex] = modifiedArea;
return true;
}
bool RvcServiceAreaDelegate::ClearSupportedAreas()
{
if (!mSupportedAreas.empty())
{
mSupportedAreas.clear();
return true;
}
return false;
}
//*************************************************************************
// Supported Maps accessors
bool RvcServiceAreaDelegate::IsSupportedMapChangeAllowed()
{
return (mIsSupportedMapChangeAllowedDeviceInstance->*mIsSupportedMapChangeAllowedCallback)();
}
uint32_t RvcServiceAreaDelegate::GetNumberOfSupportedMaps()
{
return static_cast<uint32_t>(mSupportedMaps.size());
}
bool RvcServiceAreaDelegate::GetSupportedMapByIndex(uint32_t listIndex, MapStructureWrapper & aSupportedMap)
{
if (listIndex < mSupportedMaps.size())
{
aSupportedMap = mSupportedMaps[listIndex];
return true;
}
return false;
};
bool RvcServiceAreaDelegate::GetSupportedMapById(uint32_t aMapId, uint32_t & listIndex, MapStructureWrapper & aSupportedMap)
{
// We do not need to reimplement this method as it's already done by the SDK.
// We are reimplementing this method, still using linear search, but with some optimization on the SDK implementation
// since we have direct access to the list.
listIndex = 0;
while (listIndex < mSupportedMaps.size())
{
if (mSupportedMaps[listIndex].mapID == aMapId)
{
aSupportedMap = mSupportedMaps[listIndex];
return true;
}
++listIndex;
}
return false;
};
bool RvcServiceAreaDelegate::AddSupportedMap(const MapStructureWrapper & newMap, uint32_t & listIndex)
{
// The server instance (caller) is responsible for ensuring that there are no duplicate area IDs, list size not exceeded,
// etc.
// Double-check list size to ensure there no memory issues.
if (mSupportedMaps.size() < kMaxNumSupportedMaps)
{
// not sorting list, number of areas normally expected to be small, max 255
mSupportedMaps.push_back(newMap);
listIndex = static_cast<uint32_t>(mSupportedMaps.size()) - 1; // new element is last in list
return true;
}
ChipLogError(Zcl, "AddSupportedMap %u - supported maps list is already at maximum size %u", newMap.mapID,
static_cast<uint32_t>(kMaxNumSupportedMaps));
return false;
}
bool RvcServiceAreaDelegate::ModifySupportedMap(uint32_t listIndex, const MapStructureWrapper & modifiedMap)
{
// The server instance (caller) is responsible for ensuring that there are no duplicate area IDs, list size not exceeded,
// etc.
// Double-check that mapID's match.
if (modifiedMap.mapID != mSupportedMaps[listIndex].mapID)
{
ChipLogError(Zcl, "ModifySupportedMap - mapID's do not match, new mapID %u, existing mapID %u", modifiedMap.mapID,
mSupportedMaps[listIndex].mapID);
return false;
}
// save modified map
mSupportedMaps[listIndex] = modifiedMap;
return true;
}
bool RvcServiceAreaDelegate::ClearSupportedMaps()
{
if (!mSupportedMaps.empty())
{
mSupportedMaps.clear();
return true;
}
return false;
}
//*************************************************************************
// Selected areas accessors
uint32_t RvcServiceAreaDelegate::GetNumberOfSelectedAreas()
{
return static_cast<uint32_t>(mSelectedAreas.size());
}
bool RvcServiceAreaDelegate::GetSelectedAreaByIndex(uint32_t listIndex, uint32_t & selectedArea)
{
if (listIndex < mSelectedAreas.size())
{
selectedArea = mSelectedAreas[listIndex];
return true;
}
return false;
};
bool RvcServiceAreaDelegate::AddSelectedArea(uint32_t aAreaID, uint32_t & listIndex)
{
// The server instance (caller) is responsible for ensuring that there are no duplicate area IDs, list size not exceeded,
// etc.
// Double-check list size to ensure there no memory issues.
if (mSelectedAreas.size() < kMaxNumSelectedAreas)
{
// not sorting list, number of areas normally expected to be small, max 255
mSelectedAreas.push_back(aAreaID);
listIndex = static_cast<uint32_t>(mSelectedAreas.size()) - 1; // new element is last in list
return true;
}
ChipLogError(Zcl, "AddSelectedArea %u - selected areas list is already at maximum size %u", aAreaID,
static_cast<uint32_t>(kMaxNumSelectedAreas));
return false;
}
bool RvcServiceAreaDelegate::ClearSelectedAreas()
{
if (!mSelectedAreas.empty())
{
mSelectedAreas.clear();
return true;
}
return false;
}
//*************************************************************************
// Progress List accessors
uint32_t RvcServiceAreaDelegate::GetNumberOfProgressElements()
{
return static_cast<uint32_t>(mProgressList.size());
}
bool RvcServiceAreaDelegate::GetProgressElementByIndex(uint32_t listIndex, Structs::ProgressStruct::Type & aProgressElement)
{
if (listIndex < mProgressList.size())
{
aProgressElement = mProgressList[listIndex];
return true;
}
return false;
};
bool RvcServiceAreaDelegate::GetProgressElementById(uint32_t aAreaID, uint32_t & listIndex,
Structs::ProgressStruct::Type & aProgressElement)
{
// We do not need to reimplement this method as it's already done by the SDK.
// We are reimplementing this method, still using linear search, but with some optimization on the SDK implementation
// since we have direct access to the list.
listIndex = 0;
while (listIndex < mProgressList.size())
{
if (mProgressList[listIndex].areaID == aAreaID)
{
aProgressElement = mProgressList[listIndex];
return true;
}
++listIndex;
}
return false;
};
bool RvcServiceAreaDelegate::AddProgressElement(const Structs::ProgressStruct::Type & newProgressElement, uint32_t & listIndex)
{
// The server instance (caller) is responsible for ensuring that there are no duplicate area IDs, list size not exceeded,
// etc.
// Double-check list size to ensure there no memory issues.
if (mProgressList.size() < kMaxNumProgressElements)
{
// not sorting list, number of areas normally expected to be small, max 255
mProgressList.push_back(newProgressElement);
listIndex = static_cast<uint32_t>(mProgressList.size()) - 1; // new element is last in list
return true;
}
ChipLogError(Zcl, "AddProgressElement %u -progress list is already at maximum size %u", newProgressElement.areaID,
static_cast<uint32_t>(kMaxNumProgressElements));
return false;
}
bool RvcServiceAreaDelegate::ModifyProgressElement(uint32_t listIndex,
const Structs::ProgressStruct::Type & modifiedProgressElement)
{
if (modifiedProgressElement.areaID != mProgressList[listIndex].areaID)
{
ChipLogError(Zcl, "ModifyProgressElement - areaID's do not match, new areaID %u, existing areaID %u",
modifiedProgressElement.areaID, mProgressList[listIndex].areaID);
return false;
}
mProgressList[listIndex] = modifiedProgressElement;
return true;
}
bool RvcServiceAreaDelegate::ClearProgress()
{
if (!mProgressList.empty())
{
mProgressList.clear();
return true;
}
return false;
}
void RvcServiceAreaDelegate::SetAttributesAtCleanStart()
{
if (GetNumberOfSupportedAreas() == 0)
{
return;
}
if (GetNumberOfSelectedAreas() == 0)
{
AreaStructureWrapper firstArea;
GetSupportedAreaByIndex(0, firstArea);
GetInstance()->SetCurrentArea(firstArea.areaID);
if (GetInstance()->HasFeature(Feature::kProgressReporting))
{
GetInstance()->AddPendingProgressElement(firstArea.areaID);
GetInstance()->SetProgressStatus(firstArea.areaID, OperationalStatusEnum::kOperating);
}
}
else
{
uint32_t areaId;
GetSelectedAreaByIndex(0, areaId);
GetInstance()->SetCurrentArea(areaId);
if (GetInstance()->HasFeature(Feature::kProgressReporting))
{
GetInstance()->AddPendingProgressElement(areaId);
GetInstance()->SetProgressStatus(areaId, OperationalStatusEnum::kOperating);
uint32_t i = 1;
while (GetSelectedAreaByIndex(i, areaId))
{
GetInstance()->AddPendingProgressElement(areaId);
i++;
}
}
}
}
void RvcServiceAreaDelegate::GoToNextArea(OperationalStatusEnum currentAreaOpState, bool & finished)
{
AreaStructureWrapper currentArea;
auto currentAreaIdN = GetInstance()->GetCurrentArea();
if (currentAreaIdN.IsNull())
{
ChipLogError(Zcl, "GoToNextArea: Cannot go to the next area when the current area is null.");
return;
}
if (currentAreaOpState != OperationalStatusEnum::kCompleted && currentAreaOpState != OperationalStatusEnum::kSkipped)
{
ChipLogError(Zcl, "GoToNextArea: currentAreaOpState must be either completed or skipped.");
return;
}
auto currentAreaId = currentAreaIdN.Value();
uint32_t currentAreaIndex;
GetSupportedAreaById(currentAreaId, currentAreaIndex, currentArea);
auto currentAreaMapId = currentArea.mapID;
finished = true;
if (GetInstance()->HasFeature(Feature::kProgressReporting))
{
GetInstance()->SetProgressStatus(currentAreaId, currentAreaOpState);
}
if (GetNumberOfSelectedAreas() == 0)
{
AreaStructureWrapper nextArea;
uint32_t nextIndex = currentAreaIndex + 1;
while (GetSupportedAreaByIndex(nextIndex, nextArea))
{
if (!currentAreaMapId.IsNull() && nextArea.mapID == currentAreaMapId.Value())
{
GetInstance()->SetCurrentArea(nextArea.areaID);
if (GetInstance()->HasFeature(Feature::kProgressReporting))
{
GetInstance()->SetProgressStatus(nextArea.areaID, OperationalStatusEnum::kOperating);
}
finished = false;
return;
}
++nextIndex;
}
}
else
{
uint32_t selectedAreaId;
uint32_t selectedAreaIndex = 0;
while (GetSelectedAreaByIndex(selectedAreaIndex, selectedAreaId))
{
if (selectedAreaId == currentAreaId)
{
break;
}
++selectedAreaIndex;
}
uint32_t nextSelectedAreaId;
uint32_t nextSelectedAreaIndex = selectedAreaIndex + 1;
if (GetSelectedAreaByIndex(nextSelectedAreaIndex, nextSelectedAreaId))
{
GetInstance()->SetCurrentArea(nextSelectedAreaId);
if (GetInstance()->HasFeature(Feature::kProgressReporting))
{
GetInstance()->SetProgressStatus(nextSelectedAreaId, OperationalStatusEnum::kOperating);
}
finished = false;
return;
}
}
}