Here’s a platform-agnostic recipe to add a [temperature sensor](https://www.onzuu.com/category/temperature-sensors) to your Zigbee device firmware, plus ready-to-copy snippets for Silicon Labs (EmberZNet/GSDK) and TI Z-Stack. ![ESP32-DS18B20-Temperature-Sensor-Arduino-IDE-Web-Server](https://hackmd.io/_uploads/HJ7z20v2ge.jpg) **1) What Zigbee expects (ZCL)** Use the Temperature Measurement cluster (0x0402) on your endpoint. * MeasuredValue (0x0000): int16, units = 0.01 °C * e.g., 25.34 °C → 2534; −5.2 °C → -520 * MinMeasuredValue (0x0001) and MaxMeasuredValue (0x0002): optional, int16 (0.01 °C). * Tolerance (0x0003): optional, uint16 (0.01 °C). * Invalid value (if sensor not ready): 0x8000 (int16 min). You’ll also want reporting so coordinators (Home Assistant, Zigbee2MQTT, etc.) get updates automatically. **2) Hardware & driver** Pick a [sensor](https://www.ampheo.com/c/sensors) and read it in your main loop or a periodic timer: I²C ([TMP117](https://www.ampheo.com/search/TMP117), [TMP102](https://www.ampheo.com/search/TMP102), SHTC3…), 1-Wire ([DS18B20](https://www.ampheo.com/product/ds18b20-26808200)), Analog ([LM35](https://www.ampheo.com/search/LM35_page6)/NTC via ADC), or on-chip temp sensor if available. Convert to °C, then scale to centi-degrees (×100) for ZCL. Example (pseudo, I²C TMP117): ``` float t_c = tmp117_read_celsius(i2c0); // e.g., 25.34 int16_t zcl_temp = (int16_t)lrintf(t_c * 100); // 2534 ``` **3) Push the value into ZCL & enable reporting** Workflow: 1. Read sensor → convert to int16 centi-°C. 2. Write MeasuredValue attribute on your endpoint. 3. Configure attribute reporting (min/max intervals + reportable change), or accept Configure Reporting from the coordinator. Typical reporting policy: minInterval = 30 s, maxInterval = 300 s, reportableChange = 50 (i.e., 0.5 °C). **4) [Silicon Labs](https://www.ampheo.com/manufacturer/silicon-labs) (EmberZNet / GSDK) example** a) **Add the cluster (server) to your endpoint** in the .isc/.slcp (Temperature Measurement cluster, server side). Also include Basic/Identify and (optional) Power Configuration if battery-powered. **b) Periodic read & update** ``` #include "app/framework/include/af.h" #define EP 1 static int16_t lastReported = 0x8000; static void report_temperature(int16_t centi_c) { emberAfWriteServerAttribute(EP, ZCL_TEMPERATURE_MEASUREMENT_CLUSTER_ID, ZCL_TEMPERATURE_MEASUREMENT_MEASURED_VALUE_ATTRIBUTE_ID, (uint8_t*)&centi_c, ZCL_INT16S_ATTRIBUTE_TYPE); // Notify reporting plugin that value changed emberAfReportingAttributeChangeCallback(EP, ZCL_TEMPERATURE_MEASUREMENT_CLUSTER_ID, ZCL_TEMPERATURE_MEASUREMENT_MEASURED_VALUE_ATTRIBUTE_ID, ZCL_INT16S_ATTRIBUTE_TYPE, (uint8_t*)&centi_c); } void sl_zigbee_app_tick(void) { float t_c = tmp117_read_celsius(I2C0); int16_t val = (int16_t)lrintf(t_c * 100.0f); if (val != lastReported) { report_temperature(val); lastReported = val; } } ``` **c) Configure default reporting (startup)** ``` #include "reporting.h" void app_init(void) { EmberAfPluginReportingEntry cfg = { .endpoint = EP, .clusterId = ZCL_TEMPERATURE_MEASUREMENT_CLUSTER_ID, .attributeId = ZCL_TEMPERATURE_MEASUREMENT_MEASURED_VALUE_ATTRIBUTE_ID, .mask = EMBER_ZCL_ATTRIBUTE_MASK_CLIENT | EMBER_ZCL_ATTRIBUTE_MASK_SERVER, // server attr .manufacturerCode = EMBER_AF_NULL_MANUFACTURER_CODE, .data.reportingDirection = EMBER_ZCL_REPORTING_DIRECTION_REPORTED, .data.reported.minInterval = 30, .data.reported.maxInterval = 300, .data.reported.reportableChange = 50 // 0.5°C }; emberAfPluginReportingConfigureReportedAttribute(&cfg); } ``` **5) [TI](https://www.ampheo.com/manufacturer/texas-instruments) Z-Stack (Z-Stack 3.x) example** **a) Simple Descriptor**: add Temperature Measurement (0x0402) to your endpoint’s server cluster list. **b) Attribute table:** ``` #include "zcl.h" #include "zcl_general.h" #include "zcl_ha.h" #include "zcl_ms.h" int16 MeasuredValue = 0x8000; // invalid at boot CONST zclAttrRec_t tempAttrList[] = { { ZCL_CLUSTER_ID_MS_TEMPERATURE_MEASUREMENT, { ATTRID_MS_TEMPERATURE_MEASURED_VALUE, ZCL_DATATYPE_INT16, ACCESS_CONTROL_READ | ACCESS_REPORTABLE, (void *)&MeasuredValue } }, // Optional: // { ... MinMeasuredValue }, { ... MaxMeasuredValue }, { ... Tolerance } }; ``` **c) Periodic sensor read & update:** ``` void Temp_ReadAndUpdate(void) { float t_c = tmp117_read_celsius(I2C0); int16 newVal = (int16)lrintf(t_c * 100.0f); if (newVal != MeasuredValue) { MeasuredValue = newVal; zcl_setAttrValue(ENDPOINT, ZCL_CLUSTER_ID_MS_TEMPERATURE_MEASUREMENT, ATTRID_MS_TEMPERATURE_MEASURED_VALUE, &MeasuredValue, sizeof(MeasuredValue)); // Tell BDB/reporting the value changed bdb_RepChangedAttrValue(ENDPOINT, ZCL_CLUSTER_ID_MS_TEMPERATURE_MEASUREMENT, ATTRID_MS_TEMPERATURE_MEASURED_VALUE); } } ``` **d) Reporting (default on boot or via Configure Reporting from coordinator):** ``` zclReportCmd_t rep; rep.numAttr = 1; rep.attrList[0].attrID = ATTRID_MS_TEMPERATURE_MEASURED_VALUE; rep.attrList[0].dataType = ZCL_DATATYPE_INT16; rep.attrList[0].minReportInt = 30; rep.attrList[0].maxReportInt = 300; rep.attrList[0].reportableChange = 50; // 0.5°C zcl_SendConfigReportCmd(ENDPOINT, &dstAddr, ZCL_CLUSTER_ID_MS_TEMPERATURE_MEASUREMENT, &rep, ZCL_FRAME_SERVER_CLIENT_DIR, true, bdb_getZCLFrameCounter()); ``` **6) Discovery & binding** * Ensure your endpoint exposes Basic (0x0000) and Identify (0x0003) so pairing is smooth. * During commissioning, the coordinator will discover the Temperature Measurement cluster and may send Configure Reporting. Your code above already supports that. **7) Testing checklist** * Join the device, open Zigbee2MQTT logs or a sniffer (Wireshark) and verify: * Read Attribute for 0x0402/0x0000 returns your value (e.g., 2534). * You receive Report Attributes when temperature changes or the max interval elapses. * Heat/cool the [sensor](https://www.ampheoelec.de/c/sensors) (touch with finger / freeze spray) and watch updates. * Verify negative temps format (e.g., −2.35 °C → -235). **8) Common pitfalls (quick fixes)** * Wrong units: remember centi-°C; coordinators won’t auto-scale raw °C floats. * No reports: forgot to configure reporting or to call the “attribute changed” hook. * I²C/ADC timing: don’t block Zigbee stack; read in a timer/task and keep handlers short. * Battery devices: throttle reporting (bigger minInterval, larger reportableChange) to save power; also expose Power Configuration (0x0001) for voltage/percent.