查看: 655|回复: 0

ZIGBEE report机制分析

[复制链接]

该用户从未签到

发表于 2020-11-26 16:46:42 | 显示全部楼层 |阅读模式
分享到:

ZIGBEE提供了report机制(现在只学习了send, receive还没学习)

主要目的是实现attribute属性的report功能,即提供了一种服务端和客户端数据同步的机制


以EMBER的HasampleLightSoc来具体看看report的实现过程,具体步骤如下:

1、设置report参数

  1. void emberAfMainInitCallback(void)
  2. {
  3.   EmberAfPluginReportingEntry reportingEntry;
  4.   reportingEntry.direction = EMBER_ZCL_REPORTING_DIRECTION_REPORTED;    // 设置report方向为send     
  5.   reportingEntry.endpoint = emberAfPrimaryEndpoint();            // 设置端点
  6.   reportingEntry.clusterId = ZCL_ON_OFF_CLUSTER_ID;             // 设置簇ID
  7.   reportingEntry.attributeId = ZCL_ON_OFF_ATTRIBUTE_ID;          // 设置属性ID
  8.   reportingEntry.mask = CLUSTER_MASK_SERVER;                  // 设置簇掩码(确定是服务端还是客户端)  
  9.   reportingEntry.manufacturerCode = EMBER_AF_NULL_MANUFACTURER_CODE;  // 设置厂商ID
  10.   reportingEntry.data.reported.minInterval = MIN_INTERVAL_S;       // 设置最小数据发送周期
  11.   reportingEntry.data.reported.maxInterval = MAX_INTERVAL_S;       // 设置最大数据发送周期
  12.   reportingEntry.data.reported.reportableChange = 0; // unused      
  13.   emberAfPluginReportingConfigureReportedAttribute(&reportingEntry);
  14. }
复制代码
2、report参数解析和配置过程
  1. EmberAfStatus emberAfPluginReportingConfigureReportedAttribute(const EmberAfPluginReportingEntry* newEntry)
  2. {
  3.   EmberAfAttributeMetadata *metadata;
  4.   EmberAfPluginReportingEntry entry;
  5.   EmberAfStatus status;
  6.   uint8_t i, index = NULL_INDEX;
  7.   bool initialize = true;

  8.   // Verify that we support the attribute and that the data type matches. 根据参数寻找匹配的属性,并且返回指向改属性的地址
  9.   metadata = emberAfLocateAttributeMetadata(newEntry->endpoint,
  10.                                             newEntry->clusterId,
  11.                                             newEntry->attributeId,
  12.                                             newEntry->mask,
  13.                                             newEntry->manufacturerCode);
  14.   if (metadata == NULL) {
  15.     return EMBER_ZCL_STATUS_UNSUPPORTED_ATTRIBUTE;
  16.   }

  17.   // Verify the minimum and maximum intervals make sense. 核对report周期最大值,最小值确定是否在范围内
  18.   if (newEntry->data.reported.maxInterval != 0
  19.       && (newEntry->data.reported.maxInterval
  20.           < newEntry->data.reported.minInterval)) {
  21.     return EMBER_ZCL_STATUS_INVALID_VALUE;
  22.   }

  23.   // Check the table for an entry that matches this request and also watch for
  24.   // empty slots along the way.  If a report exists, it will be overwritten
  25.   // with the new configuration.  Otherwise, a new entry will be created and
  26.   // initialized.   确定reportingTable是否有和需要配置的参数一样的,如果有在后面会更新report周期等参数,如果没有添加一个新的entry
  27.   for (i = 0; i < EMBER_AF_PLUGIN_REPORTING_TABLE_SIZE; i++) {
  28.     emAfPluginReportingGetEntry(i, &entry);
  29.     if (entry.direction == EMBER_ZCL_REPORTING_DIRECTION_REPORTED
  30.         && entry.endpoint == newEntry->endpoint
  31.         && entry.clusterId == newEntry->clusterId
  32.         && entry.attributeId == newEntry->attributeId
  33.         && entry.mask == newEntry->mask
  34.         && entry.manufacturerCode == newEntry->manufacturerCode) {
  35.       initialize = false;
  36.       index = i;
  37.       break;
  38.     } else if (entry.endpoint == EMBER_AF_PLUGIN_REPORTING_UNUSED_ENDPOINT_ID
  39.                && index == NULL_INDEX) {
  40.       index = i;
  41.     }
  42.   }

  43.   // If the maximum reporting interval is 0xFFFF, the device shall not issue
  44.   // reports for the attribute and the configuration information for that
  45.   // attribute need not be maintained.   如果maxInterval = 0xFFFF,删除该report配置
  46.   if (newEntry->data.reported.maxInterval == 0xFFFF) {
  47.     if (!initialize) {
  48.       removeConfigurationAndScheduleTick(index);
  49.     }
  50.     return EMBER_ZCL_STATUS_SUCCESS;
  51.   }

  52.   if (index == NULL_INDEX) {
  53.     return EMBER_ZCL_STATUS_INSUFFICIENT_SPACE;
  54.   } else if (initialize) {
  55.     entry.direction = EMBER_ZCL_REPORTING_DIRECTION_REPORTED;
  56.     entry.endpoint = newEntry->endpoint;
  57.     entry.clusterId = newEntry->clusterId;
  58.     entry.attributeId = newEntry->attributeId;
  59.     entry.mask = newEntry->mask;
  60.     entry.manufacturerCode = newEntry->manufacturerCode;
  61.     emAfPluginReportVolatileData[index].lastReportTimeMs = halCommonGetInt32uMillisecondTick();
  62.     emAfPluginReportVolatileData[index].lastReportValue = 0;
  63.   }

  64.   // For new or updated entries, set the intervals and reportable change.
  65.   // Updated entries will retain all other settings configured previously.   不论是新的entry还是更新旧的entry,report周期等参数需要被更新
  66.   entry.data.reported.minInterval = newEntry->data.reported.minInterval;
  67.   entry.data.reported.maxInterval = newEntry->data.reported.maxInterval;
  68.   entry.data.reported.reportableChange = newEntry->data.reported.reportableChange;

  69.   // Give the application a chance to review the configuration that we have
  70.   // been building up.  If the application rejects it, we just do not save the
  71.   // record.  If we were supposed to add a new configuration, it will not be
  72.   // created.  If we were supposed to update an existing configuration, we will
  73.   // keep the old one and just discard any changes.  So, in either case, life
  74.   // continues unchanged if the application rejects the configuration.     给应用层传递配置信息,如果应用层拒绝,配置不生效,否则配置成功,开启report功能
  75.   status = emberAfPluginReportingConfiguredCallback(&entry);
  76.   if (status == EMBER_ZCL_STATUS_SUCCESS) {
  77.     emAfPluginReportingSetEntry(index, &entry);
  78.     scheduleTick();
  79.   }
  80.   return status;
  81. }
复制代码
3、report参数设置完毕后,根据当前时间,设置的发送周期,来确定emberAfPluginReportingTickEventControl事件什么时候被激活,进而进行report的发送。
  1. static void scheduleTick(void)
  2. {
  3.   uint32_t delayMs = MAX_INT32U_VALUE;
  4.   uint8_t i;
  5.   for (i = 0; i < EMBER_AF_PLUGIN_REPORTING_TABLE_SIZE; i++) {
  6.     EmberAfPluginReportingEntry entry;
  7.     emAfPluginReportingGetEntry(i, &entry);
  8.     if (entry.endpoint != EMBER_AF_PLUGIN_REPORTING_UNUSED_ENDPOINT_ID
  9.         && entry.direction == EMBER_ZCL_REPORTING_DIRECTION_REPORTED) {
  10.       uint32_t minIntervalMs = (entry.data.reported.minInterval
  11.                                 * MILLISECOND_TICKS_PER_SECOND);
  12.       uint32_t maxIntervalMs = (entry.data.reported.maxInterval
  13.                                 * MILLISECOND_TICKS_PER_SECOND);
  14.       uint32_t elapsedMs = elapsedTimeInt32u(emAfPluginReportVolatileData[i].lastReportTimeMs,
  15.                                            halCommonGetInt32uMillisecondTick());
  16.       uint32_t remainingMs = MAX_INT32U_VALUE;
  17.       if (emAfPluginReportVolatileData[i].reportableChange) {   // 如果attribute有变化,并且两次report时间大于minInterval,则report,否则延时等待相应时间   
  18.         remainingMs = (minIntervalMs < elapsedMs
  19.                        ? 0
  20.                        : minIntervalMs - elapsedMs);
  21.       } else if (maxIntervalMs) {                               // 如果attribute没变化,并且两次report时间大于maxInterval,则report,否则延时等待相应时间
  22.         remainingMs = (maxIntervalMs < elapsedMs
  23.                        ? 0
  24.                        : maxIntervalMs - elapsedMs);
  25.       }
  26.       if (remainingMs < delayMs) {         // 寻找所有reportint table里面的延时时间中最短的一个,然后设置emberAfPluginReportingTickEventControl事件
  27.         delayMs = remainingMs;
  28.       }
  29.     }
  30.   }
  31.   if (delayMs != MAX_INT32U_VALUE) {
  32.     emberAfDebugPrintln("sched report event for: 0x%4x", delayMs);
  33.     emberAfEventControlSetDelayMS(&emberAfPluginReportingTickEventControl,
  34.                                   delayMs);
  35.   } else {
  36.     emberAfDebugPrintln("deactivate report event");
  37.     emberEventControlSetInactive(emberAfPluginReportingTickEventControl);
  38.   }
  39. }
复制代码
4、当设置了emberAfPluginReportingTickEventControl事件后,emberAfPluginReportingTickEventHandler会相应的被执行
  1. // EmberEventData structs used to populate the EmberEventData table
  2. #define EMBER_AF_GENERATED_EVENTS   \
  3.   { &emberAfIdentifyClusterServerTickCallbackControl1, emberAfIdentifyClusterServerTickCallbackWrapperFunction1 }, \
  4.   { &emberAfPluginConcentratorUpdateEventControl, emberAfPluginConcentratorUpdateEventHandler }, \
  5.   { &emberAfPluginEzmodeCommissioningStateEventControl, emberAfPluginEzmodeCommissioningStateEventHandler }, \
  6.   { &emberAfPluginFormAndJoinCleanupEventControl, emberAfPluginFormAndJoinCleanupEventHandler }, \
  7.   { &emberAfPluginIdentifyFeedbackProvideFeedbackEventControl, emberAfPluginIdentifyFeedbackProvideFeedbackEventHandler }, \
  8.   { &emberAfPluginNetworkFindTickEventControl, emberAfPluginNetworkFindTickEventHandler }, \
  9.   { &emberAfPluginReportingTickEventControl, emberAfPluginReportingTickEventHandler }, \     // 事件和相应处理函数关联表
  10.   { &buttonEventControl, buttonEventHandler }, \


  11. void emberAfPluginReportingTickEventHandler(void)
  12. {
  13.   EmberApsFrame *apsFrame = NULL;
  14.   EmberAfStatus status;
  15.   EmberAfAttributeType dataType;
  16.   uint16_t manufacturerCode;
  17.   uint8_t readData[READ_DATA_SIZE];
  18.   uint8_t i, dataSize;
  19.   bool clientToServer;
  20.   EmberBindingTableEntry bindingEntry;
  21.   uint8_t index, reportSize = 0, currentPayloadMaxLength = 0, smallestPayloadMaxLength;

  22.   for (i = 0; i < EMBER_AF_PLUGIN_REPORTING_TABLE_SIZE; i++) {
  23.     EmberAfPluginReportingEntry entry;
  24.     uint32_t elapsedMs;
  25.     emAfPluginReportingGetEntry(i, &entry);
  26.     // We will only send reports for active reported attributes and only if a
  27.     // reportable change has occurred and the minimum interval has elapsed or
  28.     // if the maximum interval is set and has elapsed.    判断当前report条件是否满足(主要是时间),如果不满足继续寻找,直到找到符合的
  29.     elapsedMs = elapsedTimeInt32u(emAfPluginReportVolatileData[i].lastReportTimeMs,
  30.                                   halCommonGetInt32uMillisecondTick());
  31.     if (entry.endpoint == EMBER_AF_PLUGIN_REPORTING_UNUSED_ENDPOINT_ID
  32.         || entry.direction != EMBER_ZCL_REPORTING_DIRECTION_REPORTED
  33.         || (elapsedMs
  34.             < entry.data.reported.minInterval * MILLISECOND_TICKS_PER_SECOND)
  35.         || (!emAfPluginReportVolatileData[i].reportableChange
  36.             && (entry.data.reported.maxInterval == 0
  37.                 || (elapsedMs
  38.                     < (entry.data.reported.maxInterval
  39.                        * MILLISECOND_TICKS_PER_SECOND))))) {
  40.       continue;
  41.     }

  42.     status = emAfReadAttribute(entry.endpoint,            //读出attribute属性
  43.                                entry.clusterId,
  44.                                entry.attributeId,
  45.                                entry.mask,
  46.                                entry.manufacturerCode,
  47.                                (uint8_t *)&readData,
  48.                                READ_DATA_SIZE,
  49.                                &dataType);
  50.     if (status != EMBER_ZCL_STATUS_SUCCESS) {
  51.       emberAfReportingPrintln("ERR: reading cluster 0x%2x attribute 0x%2x: 0x%x",
  52.                               entry.clusterId,
  53.                               entry.attributeId,
  54.                               status);
  55.       continue;
  56.     }

  57.     // find size of current report 计算reportSize = 簇ID长度+datetype长度+data长度
  58.     dataSize = (emberAfIsThisDataTypeAStringType(dataType)
  59.                   ? emberAfStringLength(readData) + 1
  60.                   : emberAfGetDataSize(dataType));
  61.     reportSize = sizeof(entry.attributeId) + sizeof(dataType) + dataSize;

  62.     // If we have already started a report for a different attribute or
  63.     // destination, or if the current entry is too big for current report, send it and create a new one.
  64.     if (apsFrame != NULL &&
  65.         (!(entry.endpoint == apsFrame->sourceEndpoint
  66.             && entry.clusterId == apsFrame->clusterId
  67.             && emberAfClusterIsClient(&entry) == clientToServer
  68.             && entry.manufacturerCode == manufacturerCode) ||
  69.         (appResponseLength + reportSize > smallestPayloadMaxLength))) {
  70.       if (appResponseLength + reportSize > smallestPayloadMaxLength) {
  71.         emberAfReportingPrintln("Reporting Entry Full - creating new report");
  72.       }
  73.       conditionallySendReport(apsFrame->sourceEndpoint, apsFrame->clusterId);
  74.       apsFrame = NULL;
  75.     }

  76.     // If we haven't made the message header, make it.
  77.     if (apsFrame == NULL) {
  78.       apsFrame = emberAfGetCommandApsFrame();
  79.       clientToServer = emberAfClusterIsClient(&entry);
  80.       // The manufacturer-specfic version of the fill API only creates a
  81.       // manufacturer-specfic command if the manufacturer code is set.  For
  82.       // non-manufacturer-specfic reports, the manufacturer code is unset, so
  83.       // we can get away with using this API for both cases.  填充zcl cammand,数据填充完成后,既可以开始发送
  84.       emberAfFillExternalManufacturerSpecificBuffer((clientToServer
  85.                                                      ? (ZCL_GLOBAL_COMMAND
  86.                                                         | ZCL_FRAME_CONTROL_CLIENT_TO_SERVER
  87.                                                         | EMBER_AF_DEFAULT_RESPONSE_POLICY_REQUESTS)
  88.                                                      : (ZCL_GLOBAL_COMMAND
  89.                                                         | ZCL_FRAME_CONTROL_SERVER_TO_CLIENT
  90.                                                         | EMBER_AF_DEFAULT_RESPONSE_POLICY_REQUESTS)),
  91.                                                     entry.clusterId,
  92.                                                     entry.manufacturerCode,
  93.                                                     ZCL_REPORT_ATTRIBUTES_COMMAND_ID,
  94.                                                     "");
  95.       apsFrame->sourceEndpoint = entry.endpoint;
  96.       apsFrame->options = EMBER_AF_DEFAULT_APS_OPTIONS;
  97.       manufacturerCode = entry.manufacturerCode;

  98.       // EMAPPFWKV2-1327: Reporting plugin does not account for reporting too many attributes
  99.       //                  in the same ZCL:ReportAttributes message

  100.       // find smallest maximum payload that the destination can receive for this cluster and source endpoint
  101.       smallestPayloadMaxLength = MAX_INT8U_VALUE;
  102.       for (index = 0; index < EMBER_BINDING_TABLE_SIZE; index++) {
  103.         status = (EmberAfStatus)emberGetBinding(index, &bindingEntry);
  104.         if (status == (EmberAfStatus)EMBER_SUCCESS && bindingEntry.local == entry.endpoint && bindingEntry.clusterId == entry.clusterId) {
  105.           currentPayloadMaxLength = emberAfMaximumApsPayloadLength(bindingEntry.type, bindingEntry.networkIndex, apsFrame);
  106.           if (currentPayloadMaxLength < smallestPayloadMaxLength) {
  107.             smallestPayloadMaxLength = currentPayloadMaxLength;
  108.           }
  109.         }
  110.       }

  111.     }

  112.     // Payload is [attribute id:2] [type:1] [data:N].
  113.     emberAfPutInt16uInResp(entry.attributeId);
  114.     emberAfPutInt8uInResp(dataType);

  115. #if (BIGENDIAN_CPU)
  116.     if (isThisDataTypeSentLittleEndianOTA(dataType)) {
  117.       uint8_t i;
  118.       for (i = 0; i < dataSize; i++) {
  119.         emberAfPutInt8uInResp(readData[dataSize - i - 1]);
  120.       }
  121.     } else {
  122.       emberAfPutBlockInResp(readData, dataSize);
  123.     }
  124. #else
  125.     emberAfPutBlockInResp(readData, dataSize);
  126. #endif

  127.     // Store the last reported time and value so that we can track intervals
  128.     // and changes.  We only track changes for data types that are small enough
  129.     // for us to compare.   记录上次report的时间和值,方便下次进行计算
  130.     emAfPluginReportVolatileData[i].reportableChange = false;
  131.     emAfPluginReportVolatileData[i].lastReportTimeMs = halCommonGetInt32uMillisecondTick();
  132.     if (dataSize <= sizeof(emAfPluginReportVolatileData[i].lastReportValue)) {
  133.       emAfPluginReportVolatileData[i].lastReportValue = 0;
  134. #if (BIGENDIAN_CPU)
  135.       MEMMOVE(((uint8_t *)&emAfPluginReportVolatileData[i].lastReportValue
  136.                + sizeof(emAfPluginReportVolatileData[i].lastReportValue)
  137.                - dataSize),
  138.               readData,
  139.               dataSize);
  140. #else
  141.       MEMMOVE(&emAfPluginReportVolatileData[i].lastReportValue, readData, dataSize);
  142. #endif
  143.     }
  144.   }

  145.   if (apsFrame != NULL) {      // 数据发送
  146.     conditionallySendReport(apsFrame->sourceEndpoint, apsFrame->clusterId);
  147.   }
  148.   scheduleTick();              // 计算下一次report时间,并且设置相应的事件,如此就可以周而复始的report数据了
  149. }
复制代码

总结:

使用report功能,只需要简单的如第一步配置相关的参数既可以了。

配置过程中注意几点:

1、配置的簇,属性,端点,厂家,掩码等参数一定要和现有的属性的相关信息一致,否则系统找不到需要发送的数据内容

2、report的最小周期和最大周期按照用户需求定制。

3、配置的过程,需要在用户需要的地方被正确的调用,例如我这里是在emberAfMainInitCallback处调用,则一上电便开启了report功能

只要相应的绑定表里面有内容,则就直接发送相应的数据到对应的绑定设备。



回复

使用道具 举报

您需要登录后才可以回帖 注册/登录

本版积分规则

关闭

站长推荐上一条 /4 下一条



手机版|小黑屋|与非网

GMT+8, 2024-11-22 09:16 , Processed in 0.106583 second(s), 15 queries , MemCache On.

ICP经营许可证 苏B2-20140176  苏ICP备14012660号-2   苏州灵动帧格网络科技有限公司 版权所有.

苏公网安备 32059002001037号

Powered by Discuz! X3.4

Copyright © 2001-2024, Tencent Cloud.