Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[app] Implement deferred attribute persister #23366

Merged
merged 3 commits into from
Nov 5, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions examples/lighting-app/nrfconnect/main/AppTask.cpp
Original file line number Diff line number Diff line change
@@ -28,6 +28,7 @@
#include <app-common/zap-generated/attribute-type.h>
#include <app-common/zap-generated/attributes/Accessors.h>
#include <app-common/zap-generated/cluster-id.h>
#include <app/DeferredAttributePersistenceProvider.h>
#include <app/clusters/identify-server/identify-server.h>
#include <app/clusters/ota-requestor/OTATestEventTriggerDelegate.h>
#include <app/server/Dnssd.h>
@@ -89,6 +90,17 @@ bool sHaveBLEConnections = false;

chip::DeviceLayer::DeviceInfoProviderImpl gExampleDeviceInfoProvider;

// Define a custom attribute persister which makes actual write of the CurrentLevel attribute value
// to the non-volatile storage only when it has remained constant for 5 seconds. This is to reduce
// the flash wearout when the attribute changes frequently as a result of MoveToLevel command.
// DeferredAttribute object describes a deferred attribute, but also holds a buffer with a value to
// be written, so it must live so long as the DeferredAttributePersistenceProvider object.
DeferredAttribute gCurrentLevelPersister(ConcreteAttributePath(kLightEndpointId, Clusters::LevelControl::Id,
Clusters::LevelControl::Attributes::CurrentLevel::Id));
DeferredAttributePersistenceProvider gDeferredAttributePersister(Server::GetInstance().GetDefaultAttributePersister(),
Span<DeferredAttribute>(&gCurrentLevelPersister, 1),
System::Clock::Milliseconds32(5000));

} // namespace

AppTask AppTask::sAppTask;
@@ -195,6 +207,7 @@ CHIP_ERROR AppTask::Init()

gExampleDeviceInfoProvider.SetStorageDelegate(&Server::GetInstance().GetPersistentStorage());
chip::DeviceLayer::SetDeviceInfoProvider(&gExampleDeviceInfoProvider);
app::SetAttributePersistenceProvider(&gDeferredAttributePersister);

ConfigurationMgr().LogDeviceConfig();
PrintOnboardingCodes(chip::RendezvousInformationFlags(chip::RendezvousInformationFlag::kBLE));
4 changes: 1 addition & 3 deletions src/app/AttributePersistenceProvider.h
Original file line number Diff line number Diff line change
@@ -37,7 +37,6 @@ class AttributePersistenceProvider
* list) to non-volatile memory.
*
* @param [in] aPath the attribute path for the data being written.
* @param [in] aMetadata the attribute metadata, as a convenience.
* @param [in] aValue the data to write. Integers and floats are
* represented in native endianness. Strings are represented
* as Pascal-style strings, as in ZCL, with a length prefix
@@ -51,8 +50,7 @@ class AttributePersistenceProvider
* of the data in the string (including the length prefix),
* which is no larger than the `size` member of aMetadata.
*/
virtual CHIP_ERROR WriteValue(const ConcreteAttributePath & aPath, const EmberAfAttributeMetadata * aMetadata,
const ByteSpan & aValue) = 0;
virtual CHIP_ERROR WriteValue(const ConcreteAttributePath & aPath, const ByteSpan & aValue) = 0;

/**
* Read an attribute value from non-volatile memory.
1 change: 1 addition & 0 deletions src/app/BUILD.gn
Original file line number Diff line number Diff line change
@@ -82,6 +82,7 @@ static_library("app") {
"CommandResponseHelper.h",
"CommandSender.cpp",
"DefaultAttributePersistenceProvider.cpp",
"DeferredAttributePersistenceProvider.cpp",
"DeviceProxy.cpp",
"DeviceProxy.h",
"EventManagement.cpp",
3 changes: 1 addition & 2 deletions src/app/DefaultAttributePersistenceProvider.cpp
Original file line number Diff line number Diff line change
@@ -22,8 +22,7 @@
namespace chip {
namespace app {

CHIP_ERROR DefaultAttributePersistenceProvider::WriteValue(const ConcreteAttributePath & aPath,
const EmberAfAttributeMetadata * aMetadata, const ByteSpan & aValue)
CHIP_ERROR DefaultAttributePersistenceProvider::WriteValue(const ConcreteAttributePath & aPath, const ByteSpan & aValue)
{
VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE);

3 changes: 1 addition & 2 deletions src/app/DefaultAttributePersistenceProvider.h
Original file line number Diff line number Diff line change
@@ -49,8 +49,7 @@ class DefaultAttributePersistenceProvider : public AttributePersistenceProvider
void Shutdown() {}

// AttributePersistenceProvider implementation.
CHIP_ERROR WriteValue(const ConcreteAttributePath & aPath, const EmberAfAttributeMetadata * aMetadata,
const ByteSpan & aValue) override;
CHIP_ERROR WriteValue(const ConcreteAttributePath & aPath, const ByteSpan & aValue) override;
CHIP_ERROR ReadValue(const ConcreteAttributePath & aPath, const EmberAfAttributeMetadata * aMetadata,
MutableByteSpan & aValue) override;

98 changes: 98 additions & 0 deletions src/app/DeferredAttributePersistenceProvider.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* Copyright (c) 2022 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/DeferredAttributePersistenceProvider.h>

#include <platform/CHIPDeviceLayer.h>

namespace chip {
namespace app {

CHIP_ERROR DeferredAttribute::PrepareWrite(System::Clock::Timestamp flushTime, const ByteSpan & value)
{
mFlushTime = flushTime;

if (mValue.AllocatedSize() != value.size())
{
mValue.Alloc(value.size());
ReturnErrorCodeIf(!mValue, CHIP_ERROR_NO_MEMORY);
}

memcpy(mValue.Get(), value.data(), value.size());
return CHIP_NO_ERROR;
}

void DeferredAttribute::Flush(AttributePersistenceProvider & persister)
{
VerifyOrReturn(IsArmed());
persister.WriteValue(mPath, ByteSpan(mValue.Get(), mValue.AllocatedSize()));
mValue.Release();
}

CHIP_ERROR DeferredAttributePersistenceProvider::WriteValue(const ConcreteAttributePath & path, const ByteSpan & value)
{
for (DeferredAttribute & da : mDeferredAttributes)
{
if (da.Matches(path))
{
ReturnErrorOnFailure(da.PrepareWrite(System::SystemClock().GetMonotonicTimestamp() + mWriteDelay, value));
FlushAndScheduleNext();
return CHIP_NO_ERROR;
}
}

return mPersister.WriteValue(path, value);
}

CHIP_ERROR DeferredAttributePersistenceProvider::ReadValue(const ConcreteAttributePath & path,
const EmberAfAttributeMetadata * metadata, MutableByteSpan & value)
{
return mPersister.ReadValue(path, metadata, value);
}

void DeferredAttributePersistenceProvider::FlushAndScheduleNext()
{
const System::Clock::Timestamp now = System::SystemClock().GetMonotonicTimestamp();
System::Clock::Timestamp nextFlushTime = System::Clock::Timestamp::max();

for (DeferredAttribute & da : mDeferredAttributes)
{
if (!da.IsArmed())
{
continue;
}

if (da.GetFlushTime() <= now)
{
da.Flush(mPersister);
}
else
{
nextFlushTime = chip::min(nextFlushTime, da.GetFlushTime());
}
}

if (nextFlushTime != System::Clock::Timestamp::max())
{
DeviceLayer::SystemLayer().StartTimer(
nextFlushTime - now,
[](System::Layer *, void * me) { static_cast<DeferredAttributePersistenceProvider *>(me)->FlushAndScheduleNext(); },
this);
}
}

} // namespace app
} // namespace chip
82 changes: 82 additions & 0 deletions src/app/DeferredAttributePersistenceProvider.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright (c) 2022 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.
*/
#pragma once

#include <app/AttributePersistenceProvider.h>
#include <lib/support/ScopedBuffer.h>
#include <lib/support/Span.h>

namespace chip {
namespace app {

class DeferredAttribute
{
public:
explicit DeferredAttribute(const ConcreteAttributePath & path) : mPath(path) {}

bool Matches(const ConcreteAttributePath & path) const { return mPath == path; }
bool IsArmed() const { return static_cast<bool>(mValue); }
System::Clock::Timestamp GetFlushTime() const { return mFlushTime; }

CHIP_ERROR PrepareWrite(System::Clock::Timestamp flushTime, const ByteSpan & value);
void Flush(AttributePersistenceProvider & persister);

private:
const ConcreteAttributePath mPath;
System::Clock::Timestamp mFlushTime;
Platform::ScopedMemoryBufferWithSize<uint8_t> mValue;
};

/**
* Decorator class for the AttributePersistenceProvider implementation that
* defers writes of selected attributes.
*
* This class is useful to increase the flash lifetime by reducing the number
* of writes of fast-changing attributes, such as CurrentLevel attribute of the
* LevelControl cluster.
*/
class DeferredAttributePersistenceProvider : public AttributePersistenceProvider
{
public:
DeferredAttributePersistenceProvider(AttributePersistenceProvider & persister,
const Span<DeferredAttribute> & deferredAttributes,
System::Clock::Milliseconds32 writeDelay) :
mPersister(persister),
mDeferredAttributes(deferredAttributes), mWriteDelay(writeDelay)
{}

/*
* If the written attribute is one of the deferred attributes specified in the constructor,
* postpone the write operation by the configured delay. If this attribute changes within the
* delay period, further postpone the operation so that the actual write happens once the
* attribute has remained constant for the write delay period.
*
* For other attributes, immediately pass the write operation to the decorated persister.
*/
CHIP_ERROR WriteValue(const ConcreteAttributePath & path, const ByteSpan & value) override;
CHIP_ERROR ReadValue(const ConcreteAttributePath & path, const EmberAfAttributeMetadata * metadata,
MutableByteSpan & value) override;

private:
void FlushAndScheduleNext();

AttributePersistenceProvider & mPersister;
const Span<DeferredAttribute> mDeferredAttributes;
const System::Clock::Milliseconds32 mWriteDelay;
};

} // namespace app
} // namespace chip
2 changes: 2 additions & 0 deletions src/app/server/Server.h
Original file line number Diff line number Diff line change
@@ -340,6 +340,8 @@ class Server

Credentials::OperationalCertificateStore * GetOpCertStore() { return mOpCertStore; }

app::DefaultAttributePersistenceProvider & GetDefaultAttributePersister() { return mAttributePersister; }

/**
* This function send the ShutDown event before stopping
* the event loop.
3 changes: 1 addition & 2 deletions src/app/util/attribute-storage.cpp
Original file line number Diff line number Diff line change
@@ -1300,8 +1300,7 @@ void emAfSaveAttributeToStorageIfNeeded(uint8_t * data, EndpointId endpoint, Clu
auto * attrStorage = app::GetAttributePersistenceProvider();
if (attrStorage)
{
attrStorage->WriteValue(app::ConcreteAttributePath(endpoint, clusterId, metadata->attributeId), metadata,
ByteSpan(data, dataSize));
attrStorage->WriteValue(app::ConcreteAttributePath(endpoint, clusterId, metadata->attributeId), ByteSpan(data, dataSize));
}
else
{