Skip to content

Commit 1ee863e

Browse files
[app] Implement deferred attribute persister (#23366)
* [app] Implement deferred attribute persister Fast-changing attributes with NVM storage, such as CurrentLevel of the LevelControl cluster, may result in rapid flash wearout using the default attribute persister which stores all values immediately. Implement a helper adapter class for the attribute persistence interface to defer writes of selected attributes. Use the new class in nRF Connect lighting-app for verification. * Code review * Add a comment
1 parent 092c91c commit 1ee863e

9 files changed

+200
-9
lines changed

examples/lighting-app/nrfconnect/main/AppTask.cpp

+13
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include <app-common/zap-generated/attribute-type.h>
2929
#include <app-common/zap-generated/attributes/Accessors.h>
3030
#include <app-common/zap-generated/cluster-id.h>
31+
#include <app/DeferredAttributePersistenceProvider.h>
3132
#include <app/clusters/identify-server/identify-server.h>
3233
#include <app/clusters/ota-requestor/OTATestEventTriggerDelegate.h>
3334
#include <app/server/Dnssd.h>
@@ -89,6 +90,17 @@ bool sHaveBLEConnections = false;
8990

9091
chip::DeviceLayer::DeviceInfoProviderImpl gExampleDeviceInfoProvider;
9192

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

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

196208
gExampleDeviceInfoProvider.SetStorageDelegate(&Server::GetInstance().GetPersistentStorage());
197209
chip::DeviceLayer::SetDeviceInfoProvider(&gExampleDeviceInfoProvider);
210+
app::SetAttributePersistenceProvider(&gDeferredAttributePersister);
198211

199212
ConfigurationMgr().LogDeviceConfig();
200213
PrintOnboardingCodes(chip::RendezvousInformationFlags(chip::RendezvousInformationFlag::kBLE));

src/app/AttributePersistenceProvider.h

+1-3
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ class AttributePersistenceProvider
3737
* list) to non-volatile memory.
3838
*
3939
* @param [in] aPath the attribute path for the data being written.
40-
* @param [in] aMetadata the attribute metadata, as a convenience.
4140
* @param [in] aValue the data to write. Integers and floats are
4241
* represented in native endianness. Strings are represented
4342
* as Pascal-style strings, as in ZCL, with a length prefix
@@ -51,8 +50,7 @@ class AttributePersistenceProvider
5150
* of the data in the string (including the length prefix),
5251
* which is no larger than the `size` member of aMetadata.
5352
*/
54-
virtual CHIP_ERROR WriteValue(const ConcreteAttributePath & aPath, const EmberAfAttributeMetadata * aMetadata,
55-
const ByteSpan & aValue) = 0;
53+
virtual CHIP_ERROR WriteValue(const ConcreteAttributePath & aPath, const ByteSpan & aValue) = 0;
5654

5755
/**
5856
* Read an attribute value from non-volatile memory.

src/app/BUILD.gn

+1
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ static_library("app") {
8282
"CommandResponseHelper.h",
8383
"CommandSender.cpp",
8484
"DefaultAttributePersistenceProvider.cpp",
85+
"DeferredAttributePersistenceProvider.cpp",
8586
"DeviceProxy.cpp",
8687
"DeviceProxy.h",
8788
"EventManagement.cpp",

src/app/DefaultAttributePersistenceProvider.cpp

+1-2
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@
2222
namespace chip {
2323
namespace app {
2424

25-
CHIP_ERROR DefaultAttributePersistenceProvider::WriteValue(const ConcreteAttributePath & aPath,
26-
const EmberAfAttributeMetadata * aMetadata, const ByteSpan & aValue)
25+
CHIP_ERROR DefaultAttributePersistenceProvider::WriteValue(const ConcreteAttributePath & aPath, const ByteSpan & aValue)
2726
{
2827
VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE);
2928

src/app/DefaultAttributePersistenceProvider.h

+1-2
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,7 @@ class DefaultAttributePersistenceProvider : public AttributePersistenceProvider
4949
void Shutdown() {}
5050

5151
// AttributePersistenceProvider implementation.
52-
CHIP_ERROR WriteValue(const ConcreteAttributePath & aPath, const EmberAfAttributeMetadata * aMetadata,
53-
const ByteSpan & aValue) override;
52+
CHIP_ERROR WriteValue(const ConcreteAttributePath & aPath, const ByteSpan & aValue) override;
5453
CHIP_ERROR ReadValue(const ConcreteAttributePath & aPath, const EmberAfAttributeMetadata * aMetadata,
5554
MutableByteSpan & aValue) override;
5655

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright (c) 2022 Project CHIP Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include <app/DeferredAttributePersistenceProvider.h>
18+
19+
#include <platform/CHIPDeviceLayer.h>
20+
21+
namespace chip {
22+
namespace app {
23+
24+
CHIP_ERROR DeferredAttribute::PrepareWrite(System::Clock::Timestamp flushTime, const ByteSpan & value)
25+
{
26+
mFlushTime = flushTime;
27+
28+
if (mValue.AllocatedSize() != value.size())
29+
{
30+
mValue.Alloc(value.size());
31+
ReturnErrorCodeIf(!mValue, CHIP_ERROR_NO_MEMORY);
32+
}
33+
34+
memcpy(mValue.Get(), value.data(), value.size());
35+
return CHIP_NO_ERROR;
36+
}
37+
38+
void DeferredAttribute::Flush(AttributePersistenceProvider & persister)
39+
{
40+
VerifyOrReturn(IsArmed());
41+
persister.WriteValue(mPath, ByteSpan(mValue.Get(), mValue.AllocatedSize()));
42+
mValue.Release();
43+
}
44+
45+
CHIP_ERROR DeferredAttributePersistenceProvider::WriteValue(const ConcreteAttributePath & path, const ByteSpan & value)
46+
{
47+
for (DeferredAttribute & da : mDeferredAttributes)
48+
{
49+
if (da.Matches(path))
50+
{
51+
ReturnErrorOnFailure(da.PrepareWrite(System::SystemClock().GetMonotonicTimestamp() + mWriteDelay, value));
52+
FlushAndScheduleNext();
53+
return CHIP_NO_ERROR;
54+
}
55+
}
56+
57+
return mPersister.WriteValue(path, value);
58+
}
59+
60+
CHIP_ERROR DeferredAttributePersistenceProvider::ReadValue(const ConcreteAttributePath & path,
61+
const EmberAfAttributeMetadata * metadata, MutableByteSpan & value)
62+
{
63+
return mPersister.ReadValue(path, metadata, value);
64+
}
65+
66+
void DeferredAttributePersistenceProvider::FlushAndScheduleNext()
67+
{
68+
const System::Clock::Timestamp now = System::SystemClock().GetMonotonicTimestamp();
69+
System::Clock::Timestamp nextFlushTime = System::Clock::Timestamp::max();
70+
71+
for (DeferredAttribute & da : mDeferredAttributes)
72+
{
73+
if (!da.IsArmed())
74+
{
75+
continue;
76+
}
77+
78+
if (da.GetFlushTime() <= now)
79+
{
80+
da.Flush(mPersister);
81+
}
82+
else
83+
{
84+
nextFlushTime = chip::min(nextFlushTime, da.GetFlushTime());
85+
}
86+
}
87+
88+
if (nextFlushTime != System::Clock::Timestamp::max())
89+
{
90+
DeviceLayer::SystemLayer().StartTimer(
91+
nextFlushTime - now,
92+
[](System::Layer *, void * me) { static_cast<DeferredAttributePersistenceProvider *>(me)->FlushAndScheduleNext(); },
93+
this);
94+
}
95+
}
96+
97+
} // namespace app
98+
} // namespace chip
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright (c) 2022 Project CHIP Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
#pragma once
17+
18+
#include <app/AttributePersistenceProvider.h>
19+
#include <lib/support/ScopedBuffer.h>
20+
#include <lib/support/Span.h>
21+
22+
namespace chip {
23+
namespace app {
24+
25+
class DeferredAttribute
26+
{
27+
public:
28+
explicit DeferredAttribute(const ConcreteAttributePath & path) : mPath(path) {}
29+
30+
bool Matches(const ConcreteAttributePath & path) const { return mPath == path; }
31+
bool IsArmed() const { return static_cast<bool>(mValue); }
32+
System::Clock::Timestamp GetFlushTime() const { return mFlushTime; }
33+
34+
CHIP_ERROR PrepareWrite(System::Clock::Timestamp flushTime, const ByteSpan & value);
35+
void Flush(AttributePersistenceProvider & persister);
36+
37+
private:
38+
const ConcreteAttributePath mPath;
39+
System::Clock::Timestamp mFlushTime;
40+
Platform::ScopedMemoryBufferWithSize<uint8_t> mValue;
41+
};
42+
43+
/**
44+
* Decorator class for the AttributePersistenceProvider implementation that
45+
* defers writes of selected attributes.
46+
*
47+
* This class is useful to increase the flash lifetime by reducing the number
48+
* of writes of fast-changing attributes, such as CurrentLevel attribute of the
49+
* LevelControl cluster.
50+
*/
51+
class DeferredAttributePersistenceProvider : public AttributePersistenceProvider
52+
{
53+
public:
54+
DeferredAttributePersistenceProvider(AttributePersistenceProvider & persister,
55+
const Span<DeferredAttribute> & deferredAttributes,
56+
System::Clock::Milliseconds32 writeDelay) :
57+
mPersister(persister),
58+
mDeferredAttributes(deferredAttributes), mWriteDelay(writeDelay)
59+
{}
60+
61+
/*
62+
* If the written attribute is one of the deferred attributes specified in the constructor,
63+
* postpone the write operation by the configured delay. If this attribute changes within the
64+
* delay period, further postpone the operation so that the actual write happens once the
65+
* attribute has remained constant for the write delay period.
66+
*
67+
* For other attributes, immediately pass the write operation to the decorated persister.
68+
*/
69+
CHIP_ERROR WriteValue(const ConcreteAttributePath & path, const ByteSpan & value) override;
70+
CHIP_ERROR ReadValue(const ConcreteAttributePath & path, const EmberAfAttributeMetadata * metadata,
71+
MutableByteSpan & value) override;
72+
73+
private:
74+
void FlushAndScheduleNext();
75+
76+
AttributePersistenceProvider & mPersister;
77+
const Span<DeferredAttribute> mDeferredAttributes;
78+
const System::Clock::Milliseconds32 mWriteDelay;
79+
};
80+
81+
} // namespace app
82+
} // namespace chip

src/app/server/Server.h

+2
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,8 @@ class Server
340340

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

343+
app::DefaultAttributePersistenceProvider & GetDefaultAttributePersister() { return mAttributePersister; }
344+
343345
/**
344346
* This function send the ShutDown event before stopping
345347
* the event loop.

src/app/util/attribute-storage.cpp

+1-2
Original file line numberDiff line numberDiff line change
@@ -1300,8 +1300,7 @@ void emAfSaveAttributeToStorageIfNeeded(uint8_t * data, EndpointId endpoint, Clu
13001300
auto * attrStorage = app::GetAttributePersistenceProvider();
13011301
if (attrStorage)
13021302
{
1303-
attrStorage->WriteValue(app::ConcreteAttributePath(endpoint, clusterId, metadata->attributeId), metadata,
1304-
ByteSpan(data, dataSize));
1303+
attrStorage->WriteValue(app::ConcreteAttributePath(endpoint, clusterId, metadata->attributeId), ByteSpan(data, dataSize));
13051304
}
13061305
else
13071306
{

0 commit comments

Comments
 (0)