Skip to content

Commit

Permalink
Merge branch 'master' into feature/easy-review-pr
Browse files Browse the repository at this point in the history
  • Loading branch information
lazarkov authored Aug 9, 2024
2 parents 29ac106 + c0b140b commit b79e144
Show file tree
Hide file tree
Showing 8 changed files with 300 additions and 158 deletions.
4 changes: 4 additions & 0 deletions examples/thermostat/linux/include/thermostat-delegate-impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ class ThermostatDelegate : public Delegate
public:
static inline ThermostatDelegate & GetInstance() { return sInstance; }

std::optional<System::Clock::Milliseconds16>
GetAtomicWriteTimeout(DataModel::DecodableList<chip::AttributeId> attributeRequests,
System::Clock::Milliseconds16 timeoutRequest) override;

CHIP_ERROR GetPresetTypeAtIndex(size_t index, Structs::PresetTypeStruct::Type & presetType) override;

uint8_t GetNumberOfPresets() override;
Expand Down
41 changes: 41 additions & 0 deletions examples/thermostat/linux/thermostat-delegate-impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,47 @@ CHIP_ERROR ThermostatDelegate::SetActivePresetHandle(const DataModel::Nullable<B
return CHIP_NO_ERROR;
}

std::optional<System::Clock::Milliseconds16>
ThermostatDelegate::GetAtomicWriteTimeout(DataModel::DecodableList<AttributeId> attributeRequests,
System::Clock::Milliseconds16 timeoutRequest)
{
auto attributeIdsIter = attributeRequests.begin();
bool requestedPresets = false, requestedSchedules = false;
while (attributeIdsIter.Next())
{
auto & attributeId = attributeIdsIter.GetValue();

switch (attributeId)
{
case Attributes::Presets::Id:
requestedPresets = true;
break;
case Attributes::Schedules::Id:
requestedSchedules = true;
break;
default:
return System::Clock::Milliseconds16(0);
}
}
if (attributeIdsIter.GetStatus() != CHIP_NO_ERROR)
{
return System::Clock::Milliseconds16(0);
}
auto timeout = System::Clock::Milliseconds16(0);
if (requestedPresets)
{
// If the client expects to edit the presets, then we'll give it 3 seconds to do so
timeout += std::chrono::milliseconds(3000);
}
if (requestedSchedules)
{
// If the client expects to edit the schedules, then we'll give it 9 seconds to do so
timeout += std::chrono::milliseconds(9000);
}
// If the client requested an even smaller timeout, then use that one
return std::min(timeoutRequest, timeout);
}

void ThermostatDelegate::InitializePendingPresets()
{
mNextFreeIndexInPendingPresetsList = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2217,6 +2217,7 @@
}; \
const EmberAfGenericClusterFunction chipFuncArrayThermostatServer[] = { \
(EmberAfGenericClusterFunction) emberAfThermostatClusterServerInitCallback, \
(EmberAfGenericClusterFunction) MatterThermostatClusterServerShutdownCallback, \
(EmberAfGenericClusterFunction) MatterThermostatClusterServerPreAttributeChangedCallback, \
}; \
const EmberAfGenericClusterFunction chipFuncArrayFanControlServer[] = { \
Expand Down Expand Up @@ -3755,7 +3756,7 @@
.attributes = ZAP_ATTRIBUTE_INDEX(616), \
.attributeCount = 26, \
.clusterSize = 72, \
.mask = ZAP_CLUSTER_MASK(SERVER) | ZAP_CLUSTER_MASK(INIT_FUNCTION) | ZAP_CLUSTER_MASK(PRE_ATTRIBUTE_CHANGED_FUNCTION), \
.mask = ZAP_CLUSTER_MASK(SERVER) | ZAP_CLUSTER_MASK(INIT_FUNCTION) | ZAP_CLUSTER_MASK(SHUTDOWN_FUNCTION) | ZAP_CLUSTER_MASK(PRE_ATTRIBUTE_CHANGED_FUNCTION), \
.functions = chipFuncArrayThermostatServer, \
.acceptedCommandList = ZAP_GENERATED_COMMANDS_INDEX( 241 ), \
.generatedCommandList = ZAP_GENERATED_COMMANDS_INDEX( 246 ), \
Expand Down
11 changes: 11 additions & 0 deletions src/app/clusters/thermostat-server/thermostat-delegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,17 @@ class Delegate

virtual ~Delegate() = default;

/**
* @brief Get the maximum timeout for atomically writing to a set of attributes
*
* @param[in] attributeRequests The list of attributes to write to.
* @param[out] timeoutRequest The timeout proposed by the client.
* @return The maximum allowed timeout; zero if the request is invalid.
*/
virtual std::optional<System::Clock::Milliseconds16>
GetAtomicWriteTimeout(DataModel::DecodableList<AttributeId> attributeRequests,
System::Clock::Milliseconds16 timeoutRequest) = 0;

/**
* @brief Get the preset type at a given index in the PresetTypes attribute
*
Expand Down
95 changes: 59 additions & 36 deletions src/app/clusters/thermostat-server/thermostat-server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
#include <app/CommandHandler.h>
#include <app/ConcreteAttributePath.h>
#include <app/ConcreteCommandPath.h>
#include <app/server/Server.h>
#include <app/util/endpoint-config-api.h>
#include <lib/core/CHIPEncoding.h>
#include <platform/internal/CHIPDeviceLayerInternal.h>

Expand Down Expand Up @@ -116,8 +118,7 @@ void TimerExpiredCallback(System::Layer * systemLayer, void * callbackContext)
VerifyOrReturn(delegate != nullptr, ChipLogError(Zcl, "Delegate is null. Unable to handle timer expired"));

delegate->ClearPendingPresetList();
gThermostatAttrAccess.SetAtomicWrite(endpoint, false);
gThermostatAttrAccess.SetAtomicWriteScopedNodeId(endpoint, ScopedNodeId());
gThermostatAttrAccess.SetAtomicWrite(endpoint, ScopedNodeId(), kAtomicWriteState_Closed);
}

/**
Expand Down Expand Up @@ -205,8 +206,7 @@ void resetAtomicWrite(Delegate * delegate, EndpointId endpoint)
delegate->ClearPendingPresetList();
}
ClearTimer(endpoint);
gThermostatAttrAccess.SetAtomicWrite(endpoint, false);
gThermostatAttrAccess.SetAtomicWriteScopedNodeId(endpoint, ScopedNodeId());
gThermostatAttrAccess.SetAtomicWrite(endpoint, ScopedNodeId(), kAtomicWriteState_Closed);
}

/**
Expand Down Expand Up @@ -605,14 +605,16 @@ void SetDefaultDelegate(EndpointId endpoint, Delegate * delegate)
}
}

void ThermostatAttrAccess::SetAtomicWrite(EndpointId endpoint, bool inProgress)
void ThermostatAttrAccess::SetAtomicWrite(EndpointId endpoint, ScopedNodeId originatorNodeId, AtomicWriteState state)
{
uint16_t ep =
emberAfGetClusterServerEndpointIndex(endpoint, Thermostat::Id, MATTER_DM_THERMOSTAT_CLUSTER_SERVER_ENDPOINT_COUNT);

if (ep < ArraySize(mAtomicWriteState))
if (ep < ArraySize(mAtomicWriteSessions))
{
mAtomicWriteState[ep] = inProgress;
mAtomicWriteSessions[ep].state = state;
mAtomicWriteSessions[ep].endpointId = endpoint;
mAtomicWriteSessions[ep].nodeId = originatorNodeId;
}
}

Expand All @@ -622,9 +624,9 @@ bool ThermostatAttrAccess::InAtomicWrite(EndpointId endpoint)
uint16_t ep =
emberAfGetClusterServerEndpointIndex(endpoint, Thermostat::Id, MATTER_DM_THERMOSTAT_CLUSTER_SERVER_ENDPOINT_COUNT);

if (ep < ArraySize(mAtomicWriteState))
if (ep < ArraySize(mAtomicWriteSessions))
{
inAtomicWrite = mAtomicWriteState[ep];
inAtomicWrite = (mAtomicWriteSessions[ep].state == kAtomicWriteState_Open);
}
return inAtomicWrite;
}
Expand All @@ -649,26 +651,15 @@ bool ThermostatAttrAccess::InAtomicWrite(CommandHandler * commandObj, EndpointId
return GetAtomicWriteScopedNodeId(endpoint) == sourceNodeId;
}

void ThermostatAttrAccess::SetAtomicWriteScopedNodeId(EndpointId endpoint, ScopedNodeId originatorNodeId)
{
uint16_t ep =
emberAfGetClusterServerEndpointIndex(endpoint, Thermostat::Id, MATTER_DM_THERMOSTAT_CLUSTER_SERVER_ENDPOINT_COUNT);

if (ep < ArraySize(mAtomicWriteNodeIds))
{
mAtomicWriteNodeIds[ep] = originatorNodeId;
}
}

ScopedNodeId ThermostatAttrAccess::GetAtomicWriteScopedNodeId(EndpointId endpoint)
{
ScopedNodeId originatorNodeId = ScopedNodeId();
uint16_t ep =
emberAfGetClusterServerEndpointIndex(endpoint, Thermostat::Id, MATTER_DM_THERMOSTAT_CLUSTER_SERVER_ENDPOINT_COUNT);

if (ep < ArraySize(mAtomicWriteNodeIds))
if (ep < ArraySize(mAtomicWriteSessions))
{
originatorNodeId = mAtomicWriteNodeIds[ep];
originatorNodeId = mAtomicWriteSessions[ep].nodeId;
}
return originatorNodeId;
}
Expand Down Expand Up @@ -704,7 +695,7 @@ CHIP_ERROR ThermostatAttrAccess::Read(const ConcreteReadAttributePath & aPath, A
}
break;
case PresetTypes::Id: {
Delegate * delegate = GetDelegate(aPath.mEndpointId);
auto delegate = GetDelegate(aPath.mEndpointId);
VerifyOrReturnError(delegate != nullptr, CHIP_ERROR_INCORRECT_STATE, ChipLogError(Zcl, "Delegate is null"));

return aEncoder.EncodeList([delegate](const auto & encoder) -> CHIP_ERROR {
Expand All @@ -723,14 +714,14 @@ CHIP_ERROR ThermostatAttrAccess::Read(const ConcreteReadAttributePath & aPath, A
}
break;
case NumberOfPresets::Id: {
Delegate * delegate = GetDelegate(aPath.mEndpointId);
auto delegate = GetDelegate(aPath.mEndpointId);
VerifyOrReturnError(delegate != nullptr, CHIP_ERROR_INCORRECT_STATE, ChipLogError(Zcl, "Delegate is null"));

ReturnErrorOnFailure(aEncoder.Encode(delegate->GetNumberOfPresets()));
}
break;
case Presets::Id: {
Delegate * delegate = GetDelegate(aPath.mEndpointId);
auto delegate = GetDelegate(aPath.mEndpointId);
VerifyOrReturnError(delegate != nullptr, CHIP_ERROR_INCORRECT_STATE, ChipLogError(Zcl, "Delegate is null"));

auto & subjectDescriptor = aEncoder.GetSubjectDescriptor();
Expand Down Expand Up @@ -766,7 +757,7 @@ CHIP_ERROR ThermostatAttrAccess::Read(const ConcreteReadAttributePath & aPath, A
}
break;
case ActivePresetHandle::Id: {
Delegate * delegate = GetDelegate(aPath.mEndpointId);
auto delegate = GetDelegate(aPath.mEndpointId);
VerifyOrReturnError(delegate != nullptr, CHIP_ERROR_INCORRECT_STATE, ChipLogError(Zcl, "Delegate is null"));

uint8_t buffer[kPresetHandleSize];
Expand Down Expand Up @@ -812,7 +803,7 @@ CHIP_ERROR ThermostatAttrAccess::Write(const ConcreteDataAttributePath & aPath,
{
case Presets::Id: {

Delegate * delegate = GetDelegate(endpoint);
auto delegate = GetDelegate(endpoint);
VerifyOrReturnError(delegate != nullptr, CHIP_ERROR_INCORRECT_STATE, ChipLogError(Zcl, "Delegate is null"));

// Presets are not editable, return INVALID_IN_STATE.
Expand Down Expand Up @@ -897,7 +888,7 @@ CHIP_ERROR ThermostatAttrAccess::Write(const ConcreteDataAttributePath & aPath,
return CHIP_NO_ERROR;
}

CHIP_ERROR ThermostatAttrAccess::AppendPendingPreset(Delegate * delegate, const PresetStruct::Type & preset)
CHIP_ERROR ThermostatAttrAccess::AppendPendingPreset(Thermostat::Delegate * delegate, const PresetStruct::Type & preset)
{
if (!IsValidPresetEntry(preset))
{
Expand Down Expand Up @@ -951,6 +942,23 @@ CHIP_ERROR ThermostatAttrAccess::AppendPendingPreset(Delegate * delegate, const
return delegate->AppendToPendingPresetList(preset);
}

void ThermostatAttrAccess::OnFabricRemoved(const FabricTable & fabricTable, FabricIndex fabricIndex)
{
for (size_t i = 0; i < ArraySize(mAtomicWriteSessions); ++i)
{
auto atomicWriteState = mAtomicWriteSessions[i];
if (atomicWriteState.state == kAtomicWriteState_Open && atomicWriteState.nodeId.GetFabricIndex() == fabricIndex)
{
auto delegate = GetDelegate(atomicWriteState.endpointId);
if (delegate == nullptr)
{
continue;
}
resetAtomicWrite(delegate, atomicWriteState.endpointId);
}
}
}

} // namespace Thermostat
} // namespace Clusters
} // namespace app
Expand Down Expand Up @@ -1394,8 +1402,6 @@ void handleAtomicBegin(CommandHandler * commandObj, const ConcreteCommandPath &
return;
}

auto timeout = commandData.timeout.Value();

if (!validAtomicAttributes(commandData, false))
{
commandObj->AddStatus(commandPath, imcode::InvalidCommand);
Expand All @@ -1412,13 +1418,18 @@ void handleAtomicBegin(CommandHandler * commandObj, const ConcreteCommandPath &
// needs to keep track of a pending preset list now.
delegate->InitializePendingPresets();

uint16_t maxTimeout = 5000;
timeout = std::min(timeout, maxTimeout);
auto timeout =
delegate->GetAtomicWriteTimeout(commandData.attributeRequests, System::Clock::Milliseconds16(commandData.timeout.Value()));

ScheduleTimer(endpoint, System::Clock::Milliseconds16(timeout));
gThermostatAttrAccess.SetAtomicWrite(endpoint, true);
gThermostatAttrAccess.SetAtomicWriteScopedNodeId(endpoint, GetSourceScopedNodeId(commandObj));
sendAtomicResponse(commandObj, commandPath, imcode::Success, imcode::Success, imcode::Success, MakeOptional(timeout));
if (!timeout.has_value())
{
commandObj->AddStatus(commandPath, imcode::InvalidCommand);
return;
}
ScheduleTimer(endpoint, timeout.value());
gThermostatAttrAccess.SetAtomicWrite(endpoint, GetSourceScopedNodeId(commandObj), kAtomicWriteState_Open);
sendAtomicResponse(commandObj, commandPath, imcode::Success, imcode::Success, imcode::Success,
MakeOptional(timeout.value().count()));
}

imcode commitPresets(Delegate * delegate, EndpointId endpoint)
Expand Down Expand Up @@ -1868,5 +1879,17 @@ bool emberAfThermostatClusterSetpointRaiseLowerCallback(app::CommandHandler * co

void MatterThermostatPluginServerInitCallback()
{
Server::GetInstance().GetFabricTable().AddFabricDelegate(&gThermostatAttrAccess);
AttributeAccessInterfaceRegistry::Instance().Register(&gThermostatAttrAccess);
}

void MatterThermostatClusterServerShutdownCallback(EndpointId endpoint)
{
ChipLogProgress(Zcl, "Shutting down thermostat server cluster on endpoint %d", endpoint);
Delegate * delegate = GetDelegate(endpoint);

if (delegate != nullptr)
{
resetAtomicWrite(delegate, endpoint);
}
}
37 changes: 21 additions & 16 deletions src/app/clusters/thermostat-server/thermostat-server.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,26 +37,22 @@ namespace Thermostat {
static constexpr size_t kThermostatEndpointCount =
MATTER_DM_THERMOSTAT_CLUSTER_SERVER_ENDPOINT_COUNT + CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT;

enum AtomicWriteState
{
kAtomicWriteState_Closed = 0,
kAtomicWriteState_Open,
};
/**
* @brief Thermostat Attribute Access Interface.
*/
class ThermostatAttrAccess : public chip::app::AttributeAccessInterface
class ThermostatAttrAccess : public chip::app::AttributeAccessInterface, public chip::FabricTable::Delegate
{
public:
ThermostatAttrAccess() : AttributeAccessInterface(Optional<chip::EndpointId>::Missing(), Thermostat::Id) {}

CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override;
CHIP_ERROR Write(const ConcreteDataAttributePath & aPath, chip::app::AttributeValueDecoder & aDecoder) override;

/**
* @brief Sets the scoped node id of the originator that sent the last successful
* AtomicRequest of type BeginWrite for the given endpoint.
*
* @param[in] endpoint The endpoint.
* @param[in] originatorNodeId The originator scoped node id.
*/
void SetAtomicWriteScopedNodeId(EndpointId endpoint, ScopedNodeId originatorNodeId);

/**
* @brief Gets the scoped node id of the originator that sent the last successful
* AtomicRequest of type BeginWrite for the given endpoint.
Expand All @@ -68,12 +64,13 @@ class ThermostatAttrAccess : public chip::app::AttributeAccessInterface
ScopedNodeId GetAtomicWriteScopedNodeId(EndpointId endpoint);

/**
* @brief Sets whether an atomic write is in progress for the given endpoint
* @brief Sets the atomic write state for the given endpoint and originatorNodeId
*
* @param[in] endpoint The endpoint.
* @param[in] inProgress Whether or not an atomic write is in progress.
* @param[in] originatorNodeId The originator scoped node id.
* @param[in] state Whether or not an atomic write is open or closed.
*/
void SetAtomicWrite(EndpointId endpoint, bool inProgress);
void SetAtomicWrite(EndpointId endpoint, ScopedNodeId originatorNodeId, AtomicWriteState state);

/**
* @brief Gets whether an atomic write is in progress for the given endpoint
Expand Down Expand Up @@ -105,10 +102,18 @@ class ThermostatAttrAccess : public chip::app::AttributeAccessInterface
bool InAtomicWrite(CommandHandler * commandObj, EndpointId endpoint);

private:
CHIP_ERROR AppendPendingPreset(Delegate * delegate, const Structs::PresetStruct::Type & preset);
CHIP_ERROR AppendPendingPreset(Thermostat::Delegate * delegate, const Structs::PresetStruct::Type & preset);

void OnFabricRemoved(const FabricTable & fabricTable, FabricIndex fabricIndex) override;

struct AtomicWriteSession
{
AtomicWriteState state = kAtomicWriteState_Closed;
ScopedNodeId nodeId;
EndpointId endpointId = kInvalidEndpointId;
};

ScopedNodeId mAtomicWriteNodeIds[kThermostatEndpointCount];
bool mAtomicWriteState[kThermostatEndpointCount];
AtomicWriteSession mAtomicWriteSessions[kThermostatEndpointCount];
};

/**
Expand Down
1 change: 1 addition & 0 deletions src/app/common/templates/config-data.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ ClustersWithShutdownFunctions:
- Color Control
- Sample MEI
- Scenes Management
- Thermostat

ClustersWithPreAttributeChangeFunctions:
- Door Lock
Expand Down
Loading

0 comments on commit b79e144

Please sign in to comment.