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