ZIGBEE report机制分析

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功能

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

以上仅供自己学习使用,有错误的地方,欢迎指正。

时间: 2024-10-09 18:23:31

ZIGBEE report机制分析的相关文章

UVM基础之---------uvm report 机制分析

uvm 中的信息报告机制相对来说比较简单,功能上来说主要分为两部分: 第一通过ID对component的信息报告冗余级别进行控制,针对每个冗余级别进行不同的行为控制.这部分工作主要由uvm_report_hander来实现: 主要涉及到的方法有get_report_verbosity_level(severity, id)/get_report_action(severity,id) == uvm_action'(UVM_NO_ACTION) 第二是对message进行格式化的输出,这部分工作主

UVM基础之-------uvm report机制的使用

后面的例子我会继续补充: 1. 因为uvm默认定义的message格式比较长,非常不利于debug过程中的分析使用,一般情况下,开始使用uvm,都要利用uvm_report_server重新定义message输出的格式.下面给出一个例子:用于将name和ID限定在同一个width. class my_report_server extends uvm_report_server; int name_width = 20; int id_width   = 20; function string

QT开发(六十三)——QT事件机制分析

QT开发(六十三)--QT事件机制分析 一.事件机制 事件是由系统或者QT平台本身在不同的时刻发出的.当用户按下鼠标.敲下键盘,或者是窗口需要重新绘制的时候,都会发出一个相应的事件.一些事件在对用户操作做出响应时发出,如键盘事件等:另一些事件则是由系统自动发出,如计时器事件. 事件的出现,使得程序代码不会按照原始的线性顺序执行.线性顺序的程序设计风格不适合处理复杂的用户交互,如用户交互过程中,用户点击"打开文件"将开始执行打开文件的操作,用户点击"保存文件"将开始执

Linux通信之poll机制分析

poll机制分析 韦东山 2009.12.10 所有的系统调用,基于都可以在它的名字前加上"sys_"前缀,这就是它在内核中对应的函数.比如系统调用open.read.write.poll,与之对应的内核函数为:sys_open.sys_read.sys_write.sys_poll. 一.内核框架: 对于系统调用poll或select,它们对应的内核函数都是sys_poll.分析sys_poll,即可理解poll机制. sys_poll函数位于fs/select.c文件中,代码如下:

Nginx处理stale事件机制分析

Nginx为提高效率采用描述符缓冲池(连接池)来处理tcp连接,一个连接对应一个读事件和一个写事件,nginx在启动的时候会创建好所用连接和事件,当事件来的时候不用再创建,然而连接池的使用却存在stale事件的问题,以下将详细分析Nginx是如何处理stale事件的,该问题涉及到epoll.Nginx连接与事件的相关知识. 1      Epoll的实现原理 epoll相关的系统调用有:epoll_create, epoll_ctl和epoll_wait.Linux-2.6.19又引入了可以屏蔽

Linux x86_64 APIC中断路由机制分析

不同CPU体系间的中断控制器工作原理有较大差异,本文是<Linux mips64r2 PCI中断路由机制分析>的姊妹篇,主要分析Broadwell-DE X86_64 APIC中断路由原理.中断配置和处理过程,并尝试回答如下问题: 为什么x86中断路由使用IO-APIC/LAPIC框架,其有什么价值? pin/irq/vector的区别.作用,取值范围和分配机制? x86_64 APIC关键概念 Pin 此处的pin特指APIC的中断输入引脚,与内外部设备的中断输入信号相连.从上图中可以看出,

[转]易语言消息机制分析(消息拦截原理)

标 题: [原创]易语言消息机制分析(消息拦截原理)作 者: 红绡枫叶时 间: 2014-12-17,12:41:44链 接: http://bbs.pediy.com/showthread.php?t=195626 我自己做了个易语言的sig签名,方便分析的时候用.易语言例子是静态编译的.版本 5.11易语言其实是基于mfc的,它依然需要mfc的消息派发机制,只不过,自己当了系统与用户间的代理人.所有的消息都要经它转发而已.我在MFC的消息派发函数_AfxDispatchCmdMsg下断点,总

Linux内核抢占实现机制分析【转】

Linux内核抢占实现机制分析 转自:http://blog.chinaunix.net/uid-24227137-id-3050754.html [摘要]本文详解了Linux内核抢占实现机制.首先介绍了内核抢占和用户抢占的概念和区别,接着分析了不可抢占内核的特点及实时系统中实现内核抢占的必要性.然后分析了禁止内核抢占的情况和内核抢占的时机,最后介绍了实现抢占内核所做的改动以及何时需要重新调度. [关键字]内核抢占,用户抢占,中断, 实时性,自旋锁,抢占时机,调度时机,schedule,pree

ffmpeg转码MPEG2-TS的音视频同步机制分析

http://blog.chinaunix.net/uid-26000296-id-3483782.html 一.FFmpeg忽略了adaptation_field()数据FFmpeg忽略了包含PCR值的adaptation_filed数据; 代码(libavformat/mpegts.c)分析如下: /* 解析TS包 */int handle_packet(MpegTSContext *ts, const uint8_t *packet){  ...   pid = AV_RB16(packe