Skip to content

Commit

Permalink
Add support for the AttributeList attribute.
Browse files Browse the repository at this point in the history
Getting this into the ZAP database as things stand involves modifying
every single cluster XML and every single cluster definition in every
single .zap file.  Instead of that, synthesize the information about
this attribute directly in the parts of codegen that need to care
about it.
  • Loading branch information
bzbarsky-apple committed Dec 7, 2021
1 parent f647393 commit a726b8d
Show file tree
Hide file tree
Showing 62 changed files with 37,325 additions and 3,974 deletions.
10 changes: 5 additions & 5 deletions src/app/AttributePathParams.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ struct AttributePathParams
//
// TODO: (#11420) This class is overlapped with ClusterInfo class, need to do a clean up.
AttributePathParams(EndpointId aEndpointId, ClusterId aClusterId) :
AttributePathParams(aEndpointId, aClusterId, ClusterInfo::kInvalidAttributeId, kInvalidListIndex)
AttributePathParams(aEndpointId, aClusterId, kInvalidAttributeId, kInvalidListIndex)
{}

AttributePathParams(EndpointId aEndpointId, ClusterId aClusterId, AttributeId aAttributeId) :
Expand Down Expand Up @@ -61,13 +61,13 @@ struct AttributePathParams
bool IsValidAttributePath() const { return HasWildcardListIndex() || !HasWildcardAttributeId(); }

inline bool HasWildcardEndpointId() const { return mEndpointId == kInvalidEndpointId; }
inline bool HasWildcardClusterId() const { return mClusterId == ClusterInfo::kInvalidClusterId; }
inline bool HasWildcardAttributeId() const { return mAttributeId == ClusterInfo::kInvalidAttributeId; }
inline bool HasWildcardClusterId() const { return mClusterId == kInvalidClusterId; }
inline bool HasWildcardAttributeId() const { return mAttributeId == kInvalidAttributeId; }
inline bool HasWildcardListIndex() const { return mListIndex == kInvalidListIndex; }

EndpointId mEndpointId = kInvalidEndpointId;
ClusterId mClusterId = ClusterInfo::kInvalidClusterId;
AttributeId mAttributeId = ClusterInfo::kInvalidAttributeId;
ClusterId mClusterId = kInvalidClusterId;
AttributeId mAttributeId = kInvalidAttributeId;
ListIndex mListIndex = kInvalidListIndex;
};
} // namespace app
Expand Down
11 changes: 0 additions & 11 deletions src/app/ClusterInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,6 @@ namespace app {
// Note: The change will happen after #11171 with a better linked list.
struct ClusterInfo
{
private:
// Allow AttributePathParams access these constants.
friend struct AttributePathParams;
friend struct EventPathParams;

// The ClusterId, AttributeId and EventId are MEIs,
// 0xFFFF is not a valid manufacturer code, thus 0xFFFF'FFFF is not a valid MEI
static constexpr ClusterId kInvalidClusterId = 0xFFFF'FFFF;
static constexpr AttributeId kInvalidAttributeId = 0xFFFF'FFFF;
static constexpr EventId kInvalidEventId = 0xFFFF'FFFF;

public:
bool IsAttributePathSupersetOf(const ClusterInfo & other) const
{
Expand Down
8 changes: 4 additions & 4 deletions src/app/EventPathParams.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@ struct EventPathParams
bool IsValidEventPath() const { return !(HasWildcardClusterId() && !HasWildcardEventId()); }

inline bool HasWildcardEndpointId() const { return mEndpointId == kInvalidEndpointId; }
inline bool HasWildcardClusterId() const { return mClusterId == ClusterInfo::kInvalidClusterId; }
inline bool HasWildcardEventId() const { return mEventId == ClusterInfo::kInvalidEventId; }
inline bool HasWildcardClusterId() const { return mClusterId == kInvalidClusterId; }
inline bool HasWildcardEventId() const { return mEventId == kInvalidEventId; }

EndpointId mEndpointId = kInvalidEndpointId;
ClusterId mClusterId = ClusterInfo::kInvalidClusterId;
EventId mEventId = ClusterInfo::kInvalidEventId;
ClusterId mClusterId = kInvalidClusterId;
EventId mEventId = kInvalidEventId;
};
} // namespace app
} // namespace chip
10 changes: 5 additions & 5 deletions src/app/tests/TestClusterInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,11 @@ void TestAttributePathIncludedDifferentClusterId(nlTestSuite * apSuite, void * a
}

/*
{ClusterInfo::kInvalidEndpointId, ClusterInfo::kInvalidClusterId, ClusterInfo::kInvalidEventId},
{ClusterInfo::kInvalidEndpointId, MockClusterId(1), ClusterInfo::kInvalidEventId},
{ClusterInfo::kInvalidEndpointId, MockClusterId(1), MockEventId(1)},
{kMockEndpoint1, ClusterInfo::kInvalidClusterId, ClusterInfo::kInvalidEventId},
{kMockEndpoint1, MockClusterId(1), ClusterInfo::kInvalidEventId},
{kInvalidEndpointId, ClusterInfo::kInvalidClusterId, ClusterInfo::kInvalidEventId},
{kInvalidEndpointId, MockClusterId(1), ClusterInfo::kInvalidEventId},
{kInvalidEndpointId, MockClusterId(1), MockEventId(1)},
{kMockEndpoint1, kInvalidClusterId, ClusterInfo::kInvalidEventId},
{kMockEndpoint1, MockClusterId(1), kInvalidEventId},
{kMockEndpoint1, MockClusterId(1), MockEventId(1)},
*/
chip::app::ClusterInfo validEventpaths[6];
Expand Down
29 changes: 29 additions & 0 deletions src/app/tests/suites/TestBasicInformation.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,32 @@ tests:
attribute: "location"
arguments:
value: ""

- label: "Read AttributeList value"
command: "readAttribute"
attribute: "AttributeList"
response:
value:
[
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
0xFFFB,
0xFFFD,
]
118 changes: 98 additions & 20 deletions src/app/util/ember-compatibility-functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,84 @@ CHIP_ERROR SendFailureStatus(const ConcreteAttributePath & aPath, AttributeRepor
return aAttributeReport.EndOfAttributeReportIB().GetError();
}

// This reader should never actually be registered; we do manual dispatch to it
// for the one attribute it handles.
class MandatoryGlobalAttributeReader : public AttributeAccessInterface
{
public:
MandatoryGlobalAttributeReader(const EmberAfCluster * aCluster) :
AttributeAccessInterface(MakeOptional(kInvalidEndpointId), kInvalidClusterId), mCluster(aCluster)
{}

protected:
const EmberAfCluster * mCluster;
};

class AttributeListReader : public MandatoryGlobalAttributeReader
{
public:
AttributeListReader(const EmberAfCluster * aCluster) : MandatoryGlobalAttributeReader(aCluster) {}

CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override;
};

CHIP_ERROR AttributeListReader::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder)
{
// The id of AttributeList is not in the attribute metadata.
// TODO: This does not play nicely with wildcard reads. Need to fix ZAP to
// put it in the metadata, or fix wildcard expansion to add it.
return aEncoder.EncodeList([this](const auto & encoder) {
constexpr AttributeId ourId = Clusters::Globals::Attributes::AttributeList::Id;
const size_t count = mCluster->attributeCount;
bool addedOurId = false;
for (size_t i = 0; i < count; ++i)
{
AttributeId id = mCluster->attributes[i].attributeId;
if (!addedOurId && id > ourId)
{
ReturnErrorOnFailure(encoder.Encode(ourId));
addedOurId = true;
}
ReturnErrorOnFailure(encoder.Encode(id));
}
if (!addedOurId)
{
ReturnErrorOnFailure(encoder.Encode(ourId));
}
return CHIP_NO_ERROR;
});
}

// Helper function for trying to read an attribute value via an
// AttributeAccessInterface. On failure, the read has failed. On success, the
// aTriedEncode outparam is set to whether the AttributeAccessInterface tried to encode a value.
CHIP_ERROR ReadViaAccessInterface(FabricIndex aAccessingFabricIndex, const ConcreteReadAttributePath & aPath,
AttributeReportIBs::Builder & aAttributeReports,
AttributeValueEncoder::AttributeEncodeState * aEncoderState,
AttributeAccessInterface * aAccessInterface, bool * aTriedEncode)
{
// TODO: We should probably clone the writer and convert failures here
// into status responses, unless our caller already does that.
AttributeValueEncoder::AttributeEncodeState state =
(aEncoderState == nullptr ? AttributeValueEncoder::AttributeEncodeState() : *aEncoderState);
AttributeValueEncoder valueEncoder(aAttributeReports, aAccessingFabricIndex, aPath, kTemporaryDataVersion, state);
CHIP_ERROR err = aAccessInterface->Read(aPath, valueEncoder);

if (err != CHIP_NO_ERROR)
{
// If the err is not CHIP_NO_ERROR, means the encoding was aborted, then the valueEncoder may save its state.
// The state is used by list chunking feature for now.
if (aEncoderState != nullptr)
{
*aEncoderState = valueEncoder.GetState();
}
return err;
}

*aTriedEncode = valueEncoder.TriedEncode();
return CHIP_NO_ERROR;
}

} // anonymous namespace

CHIP_ERROR ReadSingleClusterData(FabricIndex aAccessingFabricIndex, const ConcreteReadAttributePath & aPath,
Expand All @@ -260,6 +338,22 @@ CHIP_ERROR ReadSingleClusterData(FabricIndex aAccessingFabricIndex, const Concre
"Reading attribute: Cluster=" ChipLogFormatMEI " Endpoint=%" PRIx16 " AttributeId=" ChipLogFormatMEI,
ChipLogValueMEI(aPath.mClusterId), aPath.mEndpointId, ChipLogValueMEI(aPath.mAttributeId));

if (aPath.mAttributeId == Clusters::Globals::Attributes::AttributeList::Id)
{
// This is not in our attribute metadata, so we just check for this
// endpoint+path existing.
EmberAfCluster * cluster = emberAfFindCluster(aPath.mEndpointId, aPath.mClusterId, CLUSTER_MASK_SERVER);
if (cluster)
{
AttributeListReader reader(cluster);
bool ignored; // Our reader always tries to encode
return ReadViaAccessInterface(aAccessingFabricIndex, aPath, aAttributeReports, apEncoderState, &reader, &ignored);
}

// else to save codesize just fall through and do the metadata search
// (which we know will fail and error out);
}

EmberAfAttributeMetadata * attributeMetadata =
emberAfLocateAttributeMetadata(aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId, CLUSTER_MASK_SERVER, 0);

Expand All @@ -277,27 +371,11 @@ CHIP_ERROR ReadSingleClusterData(FabricIndex aAccessingFabricIndex, const Concre
// The AttributeValueEncoder may encode more than one AttributeReportIB for the list chunking feature.
if (attrOverride != nullptr)
{
// TODO: We should probably clone the writer and convert failures here
// into status responses, unless our caller already does that.
AttributeValueEncoder::AttributeEncodeState state =
(apEncoderState == nullptr ? AttributeValueEncoder::AttributeEncodeState() : *apEncoderState);
AttributeValueEncoder valueEncoder(aAttributeReports, aAccessingFabricIndex,
ConcreteAttributePath(aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId),
kTemporaryDataVersion, state);
CHIP_ERROR err = attrOverride->Read(aPath, valueEncoder);

if (err != CHIP_NO_ERROR)
{
// If the err is not CHIP_NO_ERROR, means the encoding was aborted, then the valueEncoder may save its state.
// The state is used by list chunking feature for now.
if (apEncoderState != nullptr)
{
*apEncoderState = valueEncoder.GetState();
}
return err;
}
bool triedEncode;
ReturnErrorOnFailure(
ReadViaAccessInterface(aAccessingFabricIndex, aPath, aAttributeReports, apEncoderState, attrOverride, &triedEncode));

if (valueEncoder.TriedEncode())
if (triedEncode)
{
return CHIP_NO_ERROR;
}
Expand Down
42 changes: 42 additions & 0 deletions src/app/zap-templates/common/ClustersHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,48 @@ Clusters.init = function(context, packageId) {
return Promise.all(promises).then(([types, clusters, commands, attributes, globalAttributes]) => {
this._clusters = clusters;
this._commands = enhancedCommands(commands, types);
// Work around ZAP's not-great support for mandatory global attributes by
// synthesizing them here.
// TODO: This should happen at the ZAP DB construction stage.
clusters.map(cluster => {
// Add AttributeList attribute.
const attrDef = {
// id should be unused, I think
id: "DO NOT USE THIS",
clusterId: cluster.id,
code: 0xFFFB,
manufacturerCode: 0,
hexCode: '0xFFFB',
name: "AttributeList",
side: "server",
type: "array",
entryType: "attrib_id",
minLength: 0,
maxLength: undefined,
min: undefined,
max: undefined,
storage: "External",
isIncluded: true,
isSingleton: false,
isBound: false,
isWritable: false,
isNullable: false,
mustUseTimedWrite: false,
defaultValue: undefined,
includedReportable: true,
minInterval: undefined,
maxInterval: undefined,
reportableChange: undefined,
define: "DO NOT USE THIS",
};
let index = attributes.findIndex(a => a.code > 0xFFFB);
if (index != -1) {
attributes.splice(index, 0, attrDef);
} else {
attributes.push(attrDef);
}
});
globalAttributes.push(0xFFFB);
this._attributes = enhancedAttributes(attributes, globalAttributes, types);

return this.ready.resolve();
Expand Down
12 changes: 12 additions & 0 deletions src/app/zap-templates/templates/app/cluster-objects.zapt
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,19 @@ namespace {{asUpperCamelCase label}} {
static constexpr bool MustUseTimedWrite() { return {{mustUseTimedWrite}}; }
};
} // namespace {{asUpperCamelCase label}}

{{#last}}
namespace AttributeList {
struct TypeInfo {
using Type = DataModel::List<AttributeId>;
using DecodableType = DataModel::DecodableList<AttributeId>;
using DecodableArgType = const DataModel::DecodableList<AttributeId> &;
static constexpr ClusterId GetClusterId() { return Clusters::{{asUpperCamelCase parent.name}}::Id; }
static constexpr AttributeId GetAttributeId() { return Attributes::AttributeList::Id; }
static constexpr bool MustUseTimedWrite() { return false; }
};
}

} // namespace Attributes
{{/last}}
{{/zcl_attributes_server}}
Expand Down
8 changes: 8 additions & 0 deletions src/app/zap-templates/templates/app/ids/Attributes.zapt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ static constexpr AttributeId Id = {{asMEI manufacturerCode code}};

{{/unless}}
{{/zcl_attributes_server}}
namespace AttributeList {
static constexpr AttributeId Id = 0x0000FFFB;
} // namespace AttributeList

} // namespace Attributes
} // namespace Globals

Expand All @@ -39,6 +43,10 @@ static constexpr AttributeId Id = Globals::Attributes::{{asUpperCamelCase label}
} // namespace {{asUpperCamelCase label}}

{{#last}}
namespace AttributeList {
static constexpr AttributeId Id = Globals::Attributes::AttributeList::Id;
} // namespace AttributeList

} // namespace Attributes
} // namespace {{asUpperCamelCase parent.label}}

Expand Down
2 changes: 1 addition & 1 deletion src/controller/java/templates/ClusterInfo-java.zapt
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ public class ClusterInfoMapping {

{{/if}}

public static class Delegated{{asUpperCamelCase name}}AttributeCallback implements ChipClusters.{{asUpperCamelCase ../name}}Cluster.{{asUpperCamelCase name}}AttributeCallback, DelegatedClusterCallback {
public static class Delegated{{asUpperCamelCase parent.name}}Cluster{{asUpperCamelCase name}}AttributeCallback implements ChipClusters.{{asUpperCamelCase ../name}}Cluster.{{asUpperCamelCase name}}AttributeCallback, DelegatedClusterCallback {
private ClusterCommandCallback callback;
@Override
public void setCallbackDelegate(ClusterCommandCallback callback) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public class ClusterReadMapping {
);
},
{{#if isList}}
() -> new ClusterInfoMapping.Delegated{{asUpperCamelCase name}}AttributeCallback(),
() -> new ClusterInfoMapping.Delegated{{asUpperCamelCase parent.name}}Cluster{{asUpperCamelCase name}}AttributeCallback(),
{{else}}
() -> new ClusterInfoMapping.Delegated{{convertAttributeCallbackTypeToJavaName chipCallback.type}}AttributeCallback(),
{{/if}}
Expand Down
Loading

0 comments on commit a726b8d

Please sign in to comment.