Skip to content

Commit 9aeda71

Browse files
committed
Make sure that attribute changed information can be propagated out of ember
1 parent 7788ad0 commit 9aeda71

10 files changed

+110
-28
lines changed

src/app/InteractionModelEngine.cpp

+3-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
#include "InteractionModelEngine.h"
2727
#include "lib/core/CHIPError.h"
28+
#include "platform/LockTracker.h"
2829

2930
#include <cinttypes>
3031

@@ -1749,7 +1750,8 @@ DataModel::Provider * InteractionModelEngine::GetDataModelProvider()
17491750
#if CHIP_CONFIG_USE_DATA_MODEL_INTERFACE
17501751
if (mDataModelProvider == nullptr)
17511752
{
1752-
// Generally this is expected to be thread safe as these should be called within the CHIP processing loop.
1753+
// These should be called within the CHIP processing loop.
1754+
assertChipStackLockedByCurrentThread();
17531755
SetDataModelProvider(CodegenDataModelProviderInstance());
17541756
}
17551757
#endif

src/app/codegen-data-model-provider/CodegenDataModelProvider_Write.cpp

+13-7
Original file line numberDiff line numberDiff line change
@@ -375,18 +375,24 @@ DataModel::ActionReturnStatus CodegenDataModelProvider::WriteAttribute(const Dat
375375
return Status::InvalidValue;
376376
}
377377

378+
AttributeChanged attributeChanged = AttributeChanged::kValueNotChanged;
379+
378380
if (request.operationFlags.Has(DataModel::OperationFlags::kInternal))
379381
{
380382
// Internal requests use the non-External interface that has less enforcement
381383
// than the external version (e.g. does not check/enforce writable settings, does not
382384
// validate attribute types) - see attribute-table.h documentation for details.
383385
status = emberAfWriteAttribute(request.path.mEndpointId, request.path.mClusterId, request.path.mAttributeId,
384-
dataBuffer.data(), (*attributeMetadata)->attributeType);
386+
dataBuffer.data(), (*attributeMetadata)->attributeType,
387+
MarkAttributeDirty::kNo, // Model handles its own dirty handling
388+
&attributeChanged);
385389
}
386390
else
387391
{
388392
status = emAfWriteAttributeExternal(request.path.mEndpointId, request.path.mClusterId, request.path.mAttributeId,
389-
dataBuffer.data(), (*attributeMetadata)->attributeType);
393+
dataBuffer.data(), (*attributeMetadata)->attributeType,
394+
MarkAttributeDirty::kNo, // Model handles its own dirty handling
395+
&attributeChanged);
390396
}
391397

392398
if (status != Protocols::InteractionModel::Status::Success)
@@ -398,11 +404,11 @@ DataModel::ActionReturnStatus CodegenDataModelProvider::WriteAttribute(const Dat
398404
//
399405
// - Internal writes may need to be able to decide if to mark things dirty or not (see AAI as well)
400406
// - Changes-ommited paths should not be marked dirty (ember is not aware of that flag)
401-
// - This likely maps to `MatterReportingAttributeChangeCallback` HOWEVER current ember write functions
402-
// will selectively call that one depending on old attribute state (i.e. calling every time is a
403-
// change in behavior)
404-
emberAfIncreaseClusterDataVersion(request.path);
405-
CurrentContext().dataModelChangeListener->MarkDirty(request.path);
407+
if (attributeChanged == AttributeChanged::kValueChanged)
408+
{
409+
emberAfIncreaseClusterDataVersion(request.path);
410+
CurrentContext().dataModelChangeListener->MarkDirty(request.path);
411+
}
406412
return CHIP_NO_ERROR;
407413
}
408414

src/app/codegen-data-model-provider/tests/EmberReadWriteOverride.cpp

+13-3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
* limitations under the License.
1616
*/
1717
#include "EmberReadWriteOverride.h"
18+
#include "app/util/af-types.h"
1819

1920
#include <app/util/attribute-storage.h>
2021
#include <app/util/ember-io-storage.h>
@@ -101,7 +102,8 @@ Status emAfReadOrWriteAttribute(const EmberAfAttributeSearchRecord * attRecord,
101102
}
102103

103104
Status emAfWriteAttributeExternal(chip::EndpointId endpoint, chip::ClusterId cluster, chip::AttributeId attributeID,
104-
uint8_t * dataPtr, EmberAfAttributeType dataType)
105+
uint8_t * dataPtr, EmberAfAttributeType dataType, chip::app::MarkAttributeDirty markDirty,
106+
chip::app::AttributeChanged * changed)
105107
{
106108
if (gEmberStatusCode != Status::Success)
107109
{
@@ -116,14 +118,22 @@ Status emAfWriteAttributeExternal(chip::EndpointId endpoint, chip::ClusterId clu
116118
// copy over as much data as possible
117119
// NOTE: we do NOT use (*metadata)->size since it is unclear if our mocks set that correctly
118120
size_t len = std::min<size_t>(sizeof(gEmberIoBuffer), chip::app::Compatibility::Internal::gEmberAttributeIOBufferSpan.size());
121+
122+
if (changed != nullptr)
123+
{
124+
*changed = (memcmp(gEmberIoBuffer, dataPtr, len) != 0) ? chip::app::AttributeChanged::kValueChanged
125+
: chip::app::AttributeChanged::kValueNotChanged;
126+
}
127+
119128
memcpy(gEmberIoBuffer, dataPtr, len);
120129
gEmberIoBufferFill = len;
121130

122131
return Status::Success;
123132
}
124133

125134
Status emberAfWriteAttribute(chip::EndpointId endpoint, chip::ClusterId cluster, chip::AttributeId attributeID, uint8_t * dataPtr,
126-
EmberAfAttributeType dataType)
135+
EmberAfAttributeType dataType, chip::app::MarkAttributeDirty markDirty,
136+
chip::app::AttributeChanged * changed)
127137
{
128-
return emAfWriteAttributeExternal(endpoint, cluster, attributeID, dataPtr, dataType);
138+
return emAfWriteAttributeExternal(endpoint, cluster, attributeID, dataPtr, dataType, markDirty, changed);
129139
}

src/app/codegen-data-model-provider/tests/TestCodegenModelViaMocks.cpp

+23-3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
* limitations under the License.
1616
*/
1717

18+
#include "lib/support/logging/TextOnlyLogging.h"
1819
#include <vector>
1920

2021
#include <pw_unit_test/framework.h>
@@ -808,6 +809,24 @@ void TestEmberScalarTypeWrite(const typename NumericAttributeTraits<T>::WorkingT
808809
model.ChangeListener().DirtyList().clear();
809810
}
810811

812+
// nullable test: write null to make sure content of buffer changed (otherwise it will be a noop for dirty checking)
813+
{
814+
TestWriteRequest test(
815+
kAdminSubjectDescriptor,
816+
ConcreteAttributePath(kMockEndpoint3, MockClusterId(4), MOCK_ATTRIBUTE_ID_FOR_NULLABLE_TYPE(ZclType)));
817+
818+
using NumericType = NumericAttributeTraits<T>;
819+
using NullableType = chip::app::DataModel::Nullable<typename NumericType::WorkingType>;
820+
AttributeValueDecoder decoder = test.DecoderFor<NullableType>(NullableType());
821+
822+
// write should succeed
823+
ASSERT_EQ(model.WriteAttribute(test.request, decoder), CHIP_NO_ERROR);
824+
825+
// dirty: we changed the value to null
826+
ASSERT_EQ(model.ChangeListener().DirtyList().size(), 1u);
827+
EXPECT_EQ(model.ChangeListener().DirtyList()[0], test.request.path);
828+
}
829+
811830
// nullable test
812831
{
813832
TestWriteRequest test(
@@ -827,8 +846,9 @@ void TestEmberScalarTypeWrite(const typename NumericAttributeTraits<T>::WorkingT
827846
typename NumericAttributeTraits<T>::WorkingType actual = NumericAttributeTraits<T>::StorageToWorking(storage);
828847

829848
ASSERT_EQ(actual, value);
830-
ASSERT_EQ(model.ChangeListener().DirtyList().size(), 1u);
831-
EXPECT_EQ(model.ChangeListener().DirtyList()[0], test.request.path);
849+
// dirty a 2nd time when we moved from null to a real value
850+
ASSERT_EQ(model.ChangeListener().DirtyList().size(), 2u);
851+
EXPECT_EQ(model.ChangeListener().DirtyList()[1], test.request.path);
832852
}
833853
}
834854

@@ -2436,7 +2456,7 @@ TEST(TestCodegenModelViaMocks, EmberWriteAttributeAccessInterfaceTest)
24362456
// AAI does not prevent read/write of regular attributes
24372457
// validate that once AAI is added, we still can go through writing regular bits (i.e.
24382458
// AAI returning "unknown" has fallback to ember)
2439-
TestEmberScalarTypeWrite<uint32_t, ZCL_INT32U_ATTRIBUTE_TYPE>(1234);
2459+
TestEmberScalarTypeWrite<uint32_t, ZCL_INT32U_ATTRIBUTE_TYPE>(4321);
24402460
TestEmberScalarNullWrite<int64_t, ZCL_INT64S_ATTRIBUTE_TYPE>();
24412461
}
24422462

src/app/util/af-types.h

+6
Original file line numberDiff line numberDiff line change
@@ -314,5 +314,11 @@ enum class MarkAttributeDirty
314314
kYes,
315315
};
316316

317+
enum class AttributeChanged
318+
{
319+
kValueNotChanged,
320+
kValueChanged,
321+
};
322+
317323
} // namespace app
318324
} // namespace chip

src/app/util/attribute-table-detail.h

+6-3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
#pragma once
1919

20+
#include <app/util/af-types.h>
2021
#include <app/util/attribute-metadata.h>
2122
#include <lib/core/DataModelTypes.h>
2223
#include <protocols/interaction_model/StatusCode.h>
@@ -27,6 +28,8 @@
2728
* This will check attribute writeability and that
2829
* the provided data type matches the expected data type.
2930
*/
30-
chip::Protocols::InteractionModel::Status emAfWriteAttributeExternal(chip::EndpointId endpoint, chip::ClusterId cluster,
31-
chip::AttributeId attributeID, uint8_t * dataPtr,
32-
EmberAfAttributeType dataType);
31+
chip::Protocols::InteractionModel::Status
32+
emAfWriteAttributeExternal(chip::EndpointId endpoint, chip::ClusterId cluster, chip::AttributeId attributeID, uint8_t * dataPtr,
33+
EmberAfAttributeType dataType,
34+
chip::app::MarkAttributeDirty markDirty = chip::app::MarkAttributeDirty::kIfChanged,
35+
chip::app::AttributeChanged * changed = nullptr);

src/app/util/attribute-table.cpp

+27-7
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
* See the License for the specific language governing permissions and
1515
* limitations under the License.
1616
*/
17+
#include "app/util/af-types.h"
1718
#include <app/util/attribute-table.h>
1819

1920
#include <app/util/attribute-table-detail.h>
@@ -167,27 +168,34 @@ int8_t emberAfCompareValues(const uint8_t * val1, const uint8_t * val2, uint16_t
167168
* - Status::Success: if the attribute was found and successfully written
168169
*/
169170
Status emAfWriteAttribute(EndpointId endpoint, ClusterId cluster, AttributeId attributeID, uint8_t * data,
170-
EmberAfAttributeType dataType, bool overrideReadOnlyAndDataType, MarkAttributeDirty markDirty);
171+
EmberAfAttributeType dataType, bool overrideReadOnlyAndDataType, MarkAttributeDirty markDirty,
172+
AttributeChanged * wasDirty);
171173
} // anonymous namespace
172174

173175
Status emAfWriteAttributeExternal(EndpointId endpoint, ClusterId cluster, AttributeId attributeID, uint8_t * dataPtr,
174-
EmberAfAttributeType dataType)
176+
EmberAfAttributeType dataType, MarkAttributeDirty markDirty, AttributeChanged * changed)
175177
{
176-
return emAfWriteAttribute(endpoint, cluster, attributeID, dataPtr, dataType, false /* override read-only */,
177-
MarkAttributeDirty::kIfChanged);
178+
return emAfWriteAttribute(endpoint, cluster, attributeID, dataPtr, dataType, false /* override read-only */, markDirty,
179+
changed);
178180
}
179181

180182
Status emberAfWriteAttribute(EndpointId endpoint, ClusterId cluster, AttributeId attributeID, uint8_t * dataPtr,
181183
EmberAfAttributeType dataType)
182184
{
183185
return emAfWriteAttribute(endpoint, cluster, attributeID, dataPtr, dataType, true /* override read-only */,
184-
MarkAttributeDirty::kIfChanged);
186+
MarkAttributeDirty::kIfChanged, nullptr);
185187
}
186188

187189
Status emberAfWriteAttribute(EndpointId endpoint, ClusterId cluster, AttributeId attributeID, uint8_t * dataPtr,
188190
EmberAfAttributeType dataType, MarkAttributeDirty markDirty)
189191
{
190-
return emAfWriteAttribute(endpoint, cluster, attributeID, dataPtr, dataType, true /* override read-only */, markDirty);
192+
return emAfWriteAttribute(endpoint, cluster, attributeID, dataPtr, dataType, true /* override read-only */, markDirty, nullptr);
193+
}
194+
195+
Status emberAfWriteAttribute(EndpointId endpoint, ClusterId cluster, AttributeId attributeID, uint8_t * dataPtr,
196+
EmberAfAttributeType dataType, MarkAttributeDirty markDirty, AttributeChanged * changed)
197+
{
198+
return emAfWriteAttribute(endpoint, cluster, attributeID, dataPtr, dataType, true /* override read-only */, markDirty, changed);
191199
}
192200

193201
//------------------------------------------------------------------------------
@@ -320,8 +328,14 @@ Status AttributeValueIsChanging(EndpointId endpoint, ClusterId cluster, Attribut
320328
}
321329

322330
Status emAfWriteAttribute(EndpointId endpoint, ClusterId cluster, AttributeId attributeID, uint8_t * data,
323-
EmberAfAttributeType dataType, bool overrideReadOnlyAndDataType, MarkAttributeDirty markDirty)
331+
EmberAfAttributeType dataType, bool overrideReadOnlyAndDataType, MarkAttributeDirty markDirty,
332+
AttributeChanged * changeState)
324333
{
334+
if (changeState)
335+
{
336+
*changeState = AttributeChanged::kValueNotChanged;
337+
}
338+
325339
const EmberAfAttributeMetadata * metadata = nullptr;
326340
EmberAfAttributeSearchRecord record;
327341
record.endpoint = endpoint;
@@ -454,6 +468,12 @@ Status emAfWriteAttribute(EndpointId endpoint, ClusterId cluster, AttributeId at
454468
return status;
455469
}
456470

471+
// internal value has changed. Report to the caller.
472+
if (changeState != nullptr)
473+
{
474+
*changeState = AttributeChanged::kValueChanged;
475+
}
476+
457477
// Save the attribute to persistent storage if needed
458478
// The callee will weed out attributes that do not need to be stored.
459479
emAfSaveAttributeToStorageIfNeeded(data, endpoint, cluster, metadata);

src/app/util/attribute-table.h

+10
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,16 @@ chip::Protocols::InteractionModel::Status emberAfWriteAttribute(chip::EndpointId
5656
EmberAfAttributeType dataType,
5757
chip::app::MarkAttributeDirty markDirty);
5858

59+
/**
60+
* A version of emberAfWriteAttribute that returns back the attribute change state (i.e. if the in-memory
61+
* value has actually changed or not).
62+
*/
63+
chip::Protocols::InteractionModel::Status emberAfWriteAttribute(chip::EndpointId endpoint, chip::ClusterId cluster,
64+
chip::AttributeId attributeID, uint8_t * dataPtr,
65+
EmberAfAttributeType dataType,
66+
chip::app::MarkAttributeDirty markDirty,
67+
chip::app::AttributeChanged * changed);
68+
5969
/**
6070
* @brief Read the attribute value, performing all the checks.
6171
*

src/app/util/mock/CodegenEmberMocks.cpp

+6-3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
* See the License for the specific language governing permissions and
1515
* limitations under the License.
1616
*/
17+
#include "app/util/af-types.h"
1718
#include <app/reporting/reporting.h>
1819
#include <app/util/attribute-storage.h>
1920

@@ -35,13 +36,15 @@ Status emAfReadOrWriteAttribute(const EmberAfAttributeSearchRecord * attRecord,
3536
}
3637

3738
Status emAfWriteAttributeExternal(chip::EndpointId endpoint, chip::ClusterId cluster, chip::AttributeId attributeID,
38-
uint8_t * dataPtr, EmberAfAttributeType dataType)
39+
uint8_t * dataPtr, EmberAfAttributeType dataType, chip::app::MarkAttributeDirty markDirty,
40+
chip::app::AttributeChanged * changed)
3941
{
4042
return Status::Success;
4143
}
4244

4345
Status emberAfWriteAttribute(chip::EndpointId endpoint, chip::ClusterId cluster, chip::AttributeId attributeID, uint8_t * dataPtr,
44-
EmberAfAttributeType dataType)
46+
EmberAfAttributeType dataType, chip::app::MarkAttributeDirty markDirty,
47+
chip::app::AttributeChanged * changed)
4548
{
46-
return emAfWriteAttributeExternal(endpoint, cluster, attributeID, dataPtr, dataType);
49+
return emAfWriteAttributeExternal(endpoint, cluster, attributeID, dataPtr, dataType, markDirty, changed);
4750
}

src/darwin/Framework/CHIP/ServerEndpoint/MTRIMDispatch.mm

+3-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ void emberAfClusterInitCallback(EndpointId endpoint, ClusterId clusterId)
4040
}
4141

4242
Protocols::InteractionModel::Status emAfWriteAttributeExternal(EndpointId endpoint, ClusterId cluster, AttributeId attributeID, uint8_t * dataPtr,
43-
EmberAfAttributeType dataType)
43+
EmberAfAttributeType dataType,
44+
chip::app::MarkAttributeDirty markDirty,
45+
chip::app::AttributeChanged * changed)
4446
{
4547
assertChipStackLockedByCurrentThread();
4648

0 commit comments

Comments
 (0)