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功能
只要相應的綁定表里面有內容,則就直接發送相應的數據到對應的綁定設備。
以上僅供自己學習使用,有錯誤的地方,歡迎指正。