ZIGBEE提供了report机制(现在只学习了send, receive还没学习) 主要目的是实现attribute属性的report功能,即提供了一种服务端和客户端数据同步的机制
以EMBER的HasampleLightSoc来具体看看report的实现过程,具体步骤如下: 1、设置report参数 - void emberAfMainInitCallback(void)
- {
- EmberAfPluginReportingEntry reportingEntry;
- reportingEntry.direction = EMBER_ZCL_REPORTING_DIRECTION_REPORTED; // 设置report方向为send
- reportingEntry.endpoint = emberAfPrimaryEndpoint(); // 设置端点
- reportingEntry.clusterId = ZCL_ON_OFF_CLUSTER_ID; // 设置簇ID
- reportingEntry.attributeId = ZCL_ON_OFF_ATTRIBUTE_ID; // 设置属性ID
- reportingEntry.mask = CLUSTER_MASK_SERVER; // 设置簇掩码(确定是服务端还是客户端)
- reportingEntry.manufacturerCode = EMBER_AF_NULL_MANUFACTURER_CODE; // 设置厂商ID
- reportingEntry.data.reported.minInterval = MIN_INTERVAL_S; // 设置最小数据发送周期
- reportingEntry.data.reported.maxInterval = MAX_INTERVAL_S; // 设置最大数据发送周期
- reportingEntry.data.reported.reportableChange = 0; // unused
- emberAfPluginReportingConfigureReportedAttribute(&reportingEntry);
- }
复制代码 2、report参数解析和配置过程- EmberAfStatus emberAfPluginReportingConfigureReportedAttribute(const EmberAfPluginReportingEntry* newEntry)
- {
- EmberAfAttributeMetadata *metadata;
- EmberAfPluginReportingEntry entry;
- EmberAfStatus status;
- uint8_t i, index = NULL_INDEX;
- bool initialize = true;
- // Verify that we support the attribute and that the data type matches. 根据参数寻找匹配的属性,并且返回指向改属性的地址
- metadata = emberAfLocateAttributeMetadata(newEntry->endpoint,
- newEntry->clusterId,
- newEntry->attributeId,
- newEntry->mask,
- newEntry->manufacturerCode);
- if (metadata == NULL) {
- return EMBER_ZCL_STATUS_UNSUPPORTED_ATTRIBUTE;
- }
- // Verify the minimum and maximum intervals make sense. 核对report周期最大值,最小值确定是否在范围内
- if (newEntry->data.reported.maxInterval != 0
- && (newEntry->data.reported.maxInterval
- < newEntry->data.reported.minInterval)) {
- return EMBER_ZCL_STATUS_INVALID_VALUE;
- }
- // Check the table for an entry that matches this request and also watch for
- // empty slots along the way. If a report exists, it will be overwritten
- // with the new configuration. Otherwise, a new entry will be created and
- // initialized. 确定reportingTable是否有和需要配置的参数一样的,如果有在后面会更新report周期等参数,如果没有添加一个新的entry
- for (i = 0; i < EMBER_AF_PLUGIN_REPORTING_TABLE_SIZE; i++) {
- emAfPluginReportingGetEntry(i, &entry);
- if (entry.direction == EMBER_ZCL_REPORTING_DIRECTION_REPORTED
- && entry.endpoint == newEntry->endpoint
- && entry.clusterId == newEntry->clusterId
- && entry.attributeId == newEntry->attributeId
- && entry.mask == newEntry->mask
- && entry.manufacturerCode == newEntry->manufacturerCode) {
- initialize = false;
- index = i;
- break;
- } else if (entry.endpoint == EMBER_AF_PLUGIN_REPORTING_UNUSED_ENDPOINT_ID
- && index == NULL_INDEX) {
- index = i;
- }
- }
- // If the maximum reporting interval is 0xFFFF, the device shall not issue
- // reports for the attribute and the configuration information for that
- // attribute need not be maintained. 如果maxInterval = 0xFFFF,删除该report配置
- if (newEntry->data.reported.maxInterval == 0xFFFF) {
- if (!initialize) {
- removeConfigurationAndScheduleTick(index);
- }
- return EMBER_ZCL_STATUS_SUCCESS;
- }
- if (index == NULL_INDEX) {
- return EMBER_ZCL_STATUS_INSUFFICIENT_SPACE;
- } else if (initialize) {
- entry.direction = EMBER_ZCL_REPORTING_DIRECTION_REPORTED;
- entry.endpoint = newEntry->endpoint;
- entry.clusterId = newEntry->clusterId;
- entry.attributeId = newEntry->attributeId;
- entry.mask = newEntry->mask;
- entry.manufacturerCode = newEntry->manufacturerCode;
- emAfPluginReportVolatileData[index].lastReportTimeMs = halCommonGetInt32uMillisecondTick();
- emAfPluginReportVolatileData[index].lastReportValue = 0;
- }
- // For new or updated entries, set the intervals and reportable change.
- // Updated entries will retain all other settings configured previously. 不论是新的entry还是更新旧的entry,report周期等参数需要被更新
- entry.data.reported.minInterval = newEntry->data.reported.minInterval;
- entry.data.reported.maxInterval = newEntry->data.reported.maxInterval;
- entry.data.reported.reportableChange = newEntry->data.reported.reportableChange;
- // Give the application a chance to review the configuration that we have
- // been building up. If the application rejects it, we just do not save the
- // record. If we were supposed to add a new configuration, it will not be
- // created. If we were supposed to update an existing configuration, we will
- // keep the old one and just discard any changes. So, in either case, life
- // continues unchanged if the application rejects the configuration. 给应用层传递配置信息,如果应用层拒绝,配置不生效,否则配置成功,开启report功能
- status = emberAfPluginReportingConfiguredCallback(&entry);
- if (status == EMBER_ZCL_STATUS_SUCCESS) {
- emAfPluginReportingSetEntry(index, &entry);
- scheduleTick();
- }
- return status;
- }
复制代码 3、report参数设置完毕后,根据当前时间,设置的发送周期,来确定emberAfPluginReportingTickEventControl事件什么时候被激活,进而进行report的发送。- static void scheduleTick(void)
- {
- uint32_t delayMs = MAX_INT32U_VALUE;
- uint8_t i;
- for (i = 0; i < EMBER_AF_PLUGIN_REPORTING_TABLE_SIZE; i++) {
- EmberAfPluginReportingEntry entry;
- emAfPluginReportingGetEntry(i, &entry);
- if (entry.endpoint != EMBER_AF_PLUGIN_REPORTING_UNUSED_ENDPOINT_ID
- && entry.direction == EMBER_ZCL_REPORTING_DIRECTION_REPORTED) {
- uint32_t minIntervalMs = (entry.data.reported.minInterval
- * MILLISECOND_TICKS_PER_SECOND);
- uint32_t maxIntervalMs = (entry.data.reported.maxInterval
- * MILLISECOND_TICKS_PER_SECOND);
- uint32_t elapsedMs = elapsedTimeInt32u(emAfPluginReportVolatileData[i].lastReportTimeMs,
- halCommonGetInt32uMillisecondTick());
- uint32_t remainingMs = MAX_INT32U_VALUE;
- if (emAfPluginReportVolatileData[i].reportableChange) { // 如果attribute有变化,并且两次report时间大于minInterval,则report,否则延时等待相应时间
- remainingMs = (minIntervalMs < elapsedMs
- ? 0
- : minIntervalMs - elapsedMs);
- } else if (maxIntervalMs) { // 如果attribute没变化,并且两次report时间大于maxInterval,则report,否则延时等待相应时间
- remainingMs = (maxIntervalMs < elapsedMs
- ? 0
- : maxIntervalMs - elapsedMs);
- }
- if (remainingMs < delayMs) { // 寻找所有reportint table里面的延时时间中最短的一个,然后设置emberAfPluginReportingTickEventControl事件
- delayMs = remainingMs;
- }
- }
- }
- if (delayMs != MAX_INT32U_VALUE) {
- emberAfDebugPrintln("sched report event for: 0x%4x", delayMs);
- emberAfEventControlSetDelayMS(&emberAfPluginReportingTickEventControl,
- delayMs);
- } else {
- emberAfDebugPrintln("deactivate report event");
- emberEventControlSetInactive(emberAfPluginReportingTickEventControl);
- }
- }
复制代码 4、当设置了emberAfPluginReportingTickEventControl事件后,emberAfPluginReportingTickEventHandler会相应的被执行- // EmberEventData structs used to populate the EmberEventData table
- #define EMBER_AF_GENERATED_EVENTS \
- { &emberAfIdentifyClusterServerTickCallbackControl1, emberAfIdentifyClusterServerTickCallbackWrapperFunction1 }, \
- { &emberAfPluginConcentratorUpdateEventControl, emberAfPluginConcentratorUpdateEventHandler }, \
- { &emberAfPluginEzmodeCommissioningStateEventControl, emberAfPluginEzmodeCommissioningStateEventHandler }, \
- { &emberAfPluginFormAndJoinCleanupEventControl, emberAfPluginFormAndJoinCleanupEventHandler }, \
- { &emberAfPluginIdentifyFeedbackProvideFeedbackEventControl, emberAfPluginIdentifyFeedbackProvideFeedbackEventHandler }, \
- { &emberAfPluginNetworkFindTickEventControl, emberAfPluginNetworkFindTickEventHandler }, \
- { &emberAfPluginReportingTickEventControl, emberAfPluginReportingTickEventHandler }, \ // 事件和相应处理函数关联表
- { &buttonEventControl, buttonEventHandler }, \
- void emberAfPluginReportingTickEventHandler(void)
- {
- EmberApsFrame *apsFrame = NULL;
- EmberAfStatus status;
- EmberAfAttributeType dataType;
- uint16_t manufacturerCode;
- uint8_t readData[READ_DATA_SIZE];
- uint8_t i, dataSize;
- bool clientToServer;
- EmberBindingTableEntry bindingEntry;
- uint8_t index, reportSize = 0, currentPayloadMaxLength = 0, smallestPayloadMaxLength;
- for (i = 0; i < EMBER_AF_PLUGIN_REPORTING_TABLE_SIZE; i++) {
- EmberAfPluginReportingEntry entry;
- uint32_t elapsedMs;
- emAfPluginReportingGetEntry(i, &entry);
- // We will only send reports for active reported attributes and only if a
- // reportable change has occurred and the minimum interval has elapsed or
- // if the maximum interval is set and has elapsed. 判断当前report条件是否满足(主要是时间),如果不满足继续寻找,直到找到符合的
- elapsedMs = elapsedTimeInt32u(emAfPluginReportVolatileData[i].lastReportTimeMs,
- halCommonGetInt32uMillisecondTick());
- if (entry.endpoint == EMBER_AF_PLUGIN_REPORTING_UNUSED_ENDPOINT_ID
- || entry.direction != EMBER_ZCL_REPORTING_DIRECTION_REPORTED
- || (elapsedMs
- < entry.data.reported.minInterval * MILLISECOND_TICKS_PER_SECOND)
- || (!emAfPluginReportVolatileData[i].reportableChange
- && (entry.data.reported.maxInterval == 0
- || (elapsedMs
- < (entry.data.reported.maxInterval
- * MILLISECOND_TICKS_PER_SECOND))))) {
- continue;
- }
- status = emAfReadAttribute(entry.endpoint, //读出attribute属性
- entry.clusterId,
- entry.attributeId,
- entry.mask,
- entry.manufacturerCode,
- (uint8_t *)&readData,
- READ_DATA_SIZE,
- &dataType);
- if (status != EMBER_ZCL_STATUS_SUCCESS) {
- emberAfReportingPrintln("ERR: reading cluster 0x%2x attribute 0x%2x: 0x%x",
- entry.clusterId,
- entry.attributeId,
- status);
- continue;
- }
- // find size of current report 计算reportSize = 簇ID长度+datetype长度+data长度
- dataSize = (emberAfIsThisDataTypeAStringType(dataType)
- ? emberAfStringLength(readData) + 1
- : emberAfGetDataSize(dataType));
- reportSize = sizeof(entry.attributeId) + sizeof(dataType) + dataSize;
- // If we have already started a report for a different attribute or
- // destination, or if the current entry is too big for current report, send it and create a new one.
- if (apsFrame != NULL &&
- (!(entry.endpoint == apsFrame->sourceEndpoint
- && entry.clusterId == apsFrame->clusterId
- && emberAfClusterIsClient(&entry) == clientToServer
- && entry.manufacturerCode == manufacturerCode) ||
- (appResponseLength + reportSize > smallestPayloadMaxLength))) {
- if (appResponseLength + reportSize > smallestPayloadMaxLength) {
- emberAfReportingPrintln("Reporting Entry Full - creating new report");
- }
- conditionallySendReport(apsFrame->sourceEndpoint, apsFrame->clusterId);
- apsFrame = NULL;
- }
- // If we haven't made the message header, make it.
- if (apsFrame == NULL) {
- apsFrame = emberAfGetCommandApsFrame();
- clientToServer = emberAfClusterIsClient(&entry);
- // The manufacturer-specfic version of the fill API only creates a
- // manufacturer-specfic command if the manufacturer code is set. For
- // non-manufacturer-specfic reports, the manufacturer code is unset, so
- // we can get away with using this API for both cases. 填充zcl cammand,数据填充完成后,既可以开始发送
- emberAfFillExternalManufacturerSpecificBuffer((clientToServer
- ? (ZCL_GLOBAL_COMMAND
- | ZCL_FRAME_CONTROL_CLIENT_TO_SERVER
- | EMBER_AF_DEFAULT_RESPONSE_POLICY_REQUESTS)
- : (ZCL_GLOBAL_COMMAND
- | ZCL_FRAME_CONTROL_SERVER_TO_CLIENT
- | EMBER_AF_DEFAULT_RESPONSE_POLICY_REQUESTS)),
- entry.clusterId,
- entry.manufacturerCode,
- ZCL_REPORT_ATTRIBUTES_COMMAND_ID,
- "");
- apsFrame->sourceEndpoint = entry.endpoint;
- apsFrame->options = EMBER_AF_DEFAULT_APS_OPTIONS;
- manufacturerCode = entry.manufacturerCode;
- // EMAPPFWKV2-1327: Reporting plugin does not account for reporting too many attributes
- // in the same ZCL:ReportAttributes message
- // find smallest maximum payload that the destination can receive for this cluster and source endpoint
- smallestPayloadMaxLength = MAX_INT8U_VALUE;
- for (index = 0; index < EMBER_BINDING_TABLE_SIZE; index++) {
- status = (EmberAfStatus)emberGetBinding(index, &bindingEntry);
- if (status == (EmberAfStatus)EMBER_SUCCESS && bindingEntry.local == entry.endpoint && bindingEntry.clusterId == entry.clusterId) {
- currentPayloadMaxLength = emberAfMaximumApsPayloadLength(bindingEntry.type, bindingEntry.networkIndex, apsFrame);
- if (currentPayloadMaxLength < smallestPayloadMaxLength) {
- smallestPayloadMaxLength = currentPayloadMaxLength;
- }
- }
- }
- }
- // Payload is [attribute id:2] [type:1] [data:N].
- emberAfPutInt16uInResp(entry.attributeId);
- emberAfPutInt8uInResp(dataType);
- #if (BIGENDIAN_CPU)
- if (isThisDataTypeSentLittleEndianOTA(dataType)) {
- uint8_t i;
- for (i = 0; i < dataSize; i++) {
- emberAfPutInt8uInResp(readData[dataSize - i - 1]);
- }
- } else {
- emberAfPutBlockInResp(readData, dataSize);
- }
- #else
- emberAfPutBlockInResp(readData, dataSize);
- #endif
- // Store the last reported time and value so that we can track intervals
- // and changes. We only track changes for data types that are small enough
- // for us to compare. 记录上次report的时间和值,方便下次进行计算
- emAfPluginReportVolatileData[i].reportableChange = false;
- emAfPluginReportVolatileData[i].lastReportTimeMs = halCommonGetInt32uMillisecondTick();
- if (dataSize <= sizeof(emAfPluginReportVolatileData[i].lastReportValue)) {
- emAfPluginReportVolatileData[i].lastReportValue = 0;
- #if (BIGENDIAN_CPU)
- MEMMOVE(((uint8_t *)&emAfPluginReportVolatileData[i].lastReportValue
- + sizeof(emAfPluginReportVolatileData[i].lastReportValue)
- - dataSize),
- readData,
- dataSize);
- #else
- MEMMOVE(&emAfPluginReportVolatileData[i].lastReportValue, readData, dataSize);
- #endif
- }
- }
- if (apsFrame != NULL) { // 数据发送
- conditionallySendReport(apsFrame->sourceEndpoint, apsFrame->clusterId);
- }
- scheduleTick(); // 计算下一次report时间,并且设置相应的事件,如此就可以周而复始的report数据了
- }
复制代码总结: 使用report功能,只需要简单的如第一步配置相关的参数既可以了。 配置过程中注意几点: 1、配置的簇,属性,端点,厂家,掩码等参数一定要和现有的属性的相关信息一致,否则系统找不到需要发送的数据内容 2、report的最小周期和最大周期按照用户需求定制。 3、配置的过程,需要在用户需要的地方被正确的调用,例如我这里是在emberAfMainInitCallback处调用,则一上电便开启了report功能 只要相应的绑定表里面有内容,则就直接发送相应的数据到对应的绑定设备。
|