Typos in Zigbee power config command macro causes battery alarms to not work

in zcl_power_config_commands.c battery alarms don't work properly due to a typo in 

#define ZB_ZCL_CHECK_THRESHOLD_EXISTENCE(ep, attr_set, is_percentage)
(is_percentage) ? \
(zb_uint16_t)(attr_set+ZB_ZCL_ATTR_POWER_CONFIG_BATTERY_PERCENTAGE_MIN_THRESHOLD_ID): \
(zb_uint16_t)(attr_set+ZB_ZCL_ATTR_POWER_CONFIG_BATTERY_VOLTAGE_MIN_THRESHOLD_ID)); \
...
(is_percentage) ? \
(zb_uint16_t)(attr_set+ZB_ZCL_ATTR_POWER_CONFIG_BATTERY_VOLTAGE_THRESHOLD1_ID): \
(zb_uint16_t)(attr_set+ZB_ZCL_ATTR_POWER_CONFIG_BATTERY_PERCENTAGE_THRESHOLD1_ID)); \
...
(is_percentage) ? \
(zb_uint16_t)(attr_set+ZB_ZCL_ATTR_POWER_CONFIG_BATTERY_VOLTAGE_THRESHOLD2_ID): \
(zb_uint16_t)(attr_set+ZB_ZCL_ATTR_POWER_CONFIG_BATTERY_PERCENTAGE_THRESHOLD2_ID)); \
...
(is_percentage) ? \
(zb_uint16_t)(attr_set+ZB_ZCL_ATTR_POWER_CONFIG_BATTERY_VOLTAGE_THRESHOLD3_ID): \
(zb_uint16_t)(attr_set+ZB_ZCL_ATTR_POWER_CONFIG_BATTERY_PERCENTAGE_THRESHOLD3_ID)); \
The macro won't set the alarm states for thresholds 1,2, and 3 because the is_percentage flag is being incorrectly used to set the comparison against the voltage. Its done correctly for alarm 0 but the others are swapped. This issue exists on ncs 2.6.0
Parents Reply Children
  • Hi, 

    It looks indeed to be a bug from DSR's side. We've created a ticket to patch this from their end. jira.dsr-corporation.com/.../ZOINOR-11

    Kind regards,
    Andreas

  • Hi again,

    I can also add that the fix will be implemented in NCS 2.7.0, and the fix which will be outside of the ZBOSS library can be seen in the attached file. 

    I assume that you have your own fix for this, but if you're waiting you can use the fix below if you want to

    /* ZBOSS Zigbee software protocol stack
     *
     * Copyright (c) 2012-2020 DSR Corporation, Denver CO, USA.
     * www.dsr-zboss.com
     * www.dsr-corporation.com
     * All rights reserved.
     *
     * This is unpublished proprietary source code of DSR Corporation
     * The copyright notice does not evidence any actual or intended
     * publication of such source code.
     *
     * ZBOSS is a registered trademark of Data Storage Research LLC d/b/a DSR
     * Corporation
     *
     * Commercial Usage
     * Licensees holding valid DSR Commercial licenses may use
     * this file in accordance with the DSR Commercial License
     * Agreement provided with the Software or, alternatively, in accordance
     * with the terms contained in a written agreement between you and
     * DSR.
     */
    /* PURPOSE: ZCL Power Configuration cluster specific commands handling
    */
    
    #define ZB_TRACE_FILE_ID 2079
    
    #include "zb_common.h"
    
    #if defined (ZB_ZCL_SUPPORT_CLUSTER_POWER_CONFIG)
    
    #include "zb_time.h"
    #include "zb_zdo.h"
    #include "zb_zcl.h"
    #include "zcl/zb_zcl_power_config.h"
    #include "zcl/zb_zcl_alarms.h"
    
    /** @internal
        @{
    */
    
    zb_ret_t check_value_power_config_server(zb_uint16_t attr_id, zb_uint8_t endpoint, zb_uint8_t *value);
    void zb_zcl_power_config_write_attr_hook_server(zb_uint8_t endpoint, zb_uint16_t attr_id, zb_uint8_t *new_value, zb_uint16_t manuf_code);
    
    void zb_zcl_power_config_init_server()
    {
      zb_zcl_add_cluster_handlers(ZB_ZCL_CLUSTER_ID_POWER_CONFIG,
                                  ZB_ZCL_CLUSTER_SERVER_ROLE,
                                  check_value_power_config_server,
                                  zb_zcl_power_config_write_attr_hook_server,
                                  (zb_zcl_cluster_handler_t)NULL);
    }
    
    void zb_zcl_power_config_init_client()
    {
      zb_zcl_add_cluster_handlers(ZB_ZCL_CLUSTER_ID_POWER_CONFIG,
                                  ZB_ZCL_CLUSTER_CLIENT_ROLE,
                                  (zb_zcl_cluster_check_value_t)NULL,
                                  (zb_zcl_cluster_write_attr_hook_t)NULL,
                                  (zb_zcl_cluster_handler_t)NULL);
    }
    
    zb_ret_t check_value_power_config_server(zb_uint16_t attr_id, zb_uint8_t endpoint, zb_uint8_t *value)
    {
      ZVUNUSED(attr_id);
      ZVUNUSED(value);
      ZVUNUSED(endpoint);
    
      /* All values for mandatory attributes are allowed, extra check for
       * optional attributes is needed */
    
      return RET_OK;
    }
    
    /*Subtrahend to cast battery voltage attribute IDs to the simplified calculation view*/
    #define ZB_ZCL_POWER_CONFIG_BATTERY_VOLTAGE_ATTR_SUBTRAHEND 0x20
    /*Subtrahend to cast battery percentage remaining attribute IDs to the simplified calculation view*/
    #define ZB_ZCL_POWER_CONFIG_BATTERY_PERCENTAGE_REMAINING_ATTR_SUBTRAHEND 0x21
    /*The addend is needed for calculation alarm value from normalized attribute Id*/
    #define ZB_ZCL_POWER_CONFIG_BATTERY_ALARM_CODES_ADDEND 0x20
    
    /*Alarm State attribute shifts (ZCL spec Rev 7 3.3.2.2.4.11)*/
    #define ZB_ZCL_POWER_CONFIG_ALARM_STATE_ATTR_BATTERY_SOURCE_LSHIFT 0
    #define ZB_ZCL_POWER_CONFIG_ALARM_STATE_ATTR_BATTERY_SOURCE_2_LSHIFT 10
    #define ZB_ZCL_POWER_CONFIG_ALARM_STATE_ATTR_BATTERY_SOURCE_3_LSHIFT 20
    
    /*The minuend for getting alarm mask value from normalized attribute ID*/
    #define ZB_ZCL_POWER_CONFIG_ALARM_MASKS_MINUEND 16
    
    #define ZB_ZCL_CHECK_THRESHOLD_EXISTENCE(ep, attr_set, is_percentage)                       \
    {                                                                                           \
      zb_zcl_attr_t *attr_desc_local;                                                           \
      attr_desc_local = zb_zcl_get_attr_desc_a(                                                 \
        ep,                                                                                     \
        ZB_ZCL_CLUSTER_ID_POWER_CONFIG,                                                         \
        ZB_ZCL_CLUSTER_SERVER_ROLE,                                                             \
        (is_percentage) ?                                                                       \
        (zb_uint16_t)(attr_set+ZB_ZCL_ATTR_POWER_CONFIG_BATTERY_PERCENTAGE_MIN_THRESHOLD_ID):   \
        (zb_uint16_t)(attr_set+ZB_ZCL_ATTR_POWER_CONFIG_BATTERY_VOLTAGE_MIN_THRESHOLD_ID));     \
      if (!attr_desc_local)                                                                     \
      {                                                                                         \
        val &= ~ZB_ZCL_POWER_CONFIG_BATTERY_ALARM_STATE_SOURCE1_MIN_THRESHOLD;                  \
      }                                                                                         \
      attr_desc_local = zb_zcl_get_attr_desc_a(                                                 \
        ep,                                                                                     \
        ZB_ZCL_CLUSTER_ID_POWER_CONFIG,                                                         \
        ZB_ZCL_CLUSTER_SERVER_ROLE,                                                             \
        (is_percentage) ?                                                                       \
        (zb_uint16_t)(attr_set+ZB_ZCL_ATTR_POWER_CONFIG_BATTERY_PERCENTAGE_THRESHOLD1_ID):      \
        (zb_uint16_t)(attr_set+ZB_ZCL_ATTR_POWER_CONFIG_BATTERY_VOLTAGE_THRESHOLD1_ID));        \
      if (!attr_desc_local)                                                                     \
      {                                                                                         \
        val &= ~ZB_ZCL_POWER_CONFIG_BATTERY_ALARM_STATE_SOURCE1_VOLTAGE1;                       \
      }                                                                                         \
      attr_desc_local = zb_zcl_get_attr_desc_a(                                                 \
        ep,                                                                                     \
        ZB_ZCL_CLUSTER_ID_POWER_CONFIG,                                                         \
        ZB_ZCL_CLUSTER_SERVER_ROLE,                                                             \
        (is_percentage) ?                                                                       \
        (zb_uint16_t)(attr_set+ZB_ZCL_ATTR_POWER_CONFIG_BATTERY_PERCENTAGE_THRESHOLD2_ID):      \
        (zb_uint16_t)(attr_set+ZB_ZCL_ATTR_POWER_CONFIG_BATTERY_VOLTAGE_THRESHOLD2_ID));        \
      if (!attr_desc_local)                                                                     \
      {                                                                                         \
        val &= ~ZB_ZCL_POWER_CONFIG_BATTERY_ALARM_STATE_SOURCE1_VOLTAGE2;                       \
      }                                                                                         \
      attr_desc_local = zb_zcl_get_attr_desc_a(                                                 \
        ep,                                                                                     \
        ZB_ZCL_CLUSTER_ID_POWER_CONFIG,                                                         \
        ZB_ZCL_CLUSTER_SERVER_ROLE,                                                             \
        (is_percentage) ?                                                                       \
        (zb_uint16_t)(attr_set+ZB_ZCL_ATTR_POWER_CONFIG_BATTERY_PERCENTAGE_THRESHOLD3_ID):      \
        (zb_uint16_t)(attr_set+ZB_ZCL_ATTR_POWER_CONFIG_BATTERY_VOLTAGE_THRESHOLD3_ID));        \
      if (!attr_desc_local)                                                                     \
      {                                                                                         \
        val &= ~ZB_ZCL_POWER_CONFIG_BATTERY_ALARM_STATE_SOURCE1_VOLTAGE3;                       \
      }                                                                                         \
    }
    
    void fill_alarm_mask_bitmap(zb_uint8_t ep, enum zb_zcl_power_config_battery_alarm_state_e value,
                                     zb_zcl_attr_t *attr_desc, zb_uint16_t attr_set, zb_bool_t is_percentage)
    {
      if (attr_desc)
      {
        zb_uint8_t val = ZB_ZCL_POWER_CONFIG_ALARM_MASKS_MINUEND - value;
        ZB_ZCL_CHECK_THRESHOLD_EXISTENCE(ep, attr_set, is_percentage);
        ZB_ZCL_SET_DIRECTLY_ATTR_VAL8(attr_desc, val);
      }
    }
    
    void fill_alarm_state_bitmap(zb_uint8_t ep, enum zb_zcl_power_config_battery_alarm_state_e value,
                                      zb_zcl_attr_t *attr_desc, zb_uint16_t attr_set, zb_bool_t is_percentage)
    {
      if (attr_desc)
      {
        zb_uint32_t attr_val = 0;
        zb_uint8_t val = ZB_ZCL_POWER_CONFIG_ALARM_MASKS_MINUEND - value;
        ZB_ZCL_CHECK_THRESHOLD_EXISTENCE(ep, attr_set, is_percentage);
        if (attr_set == ZB_ZCL_POWER_CONFIG_BATTERY_ATTRIBUTE_SET)
        {
          attr_val = (zb_uint32_t)val << ZB_ZCL_POWER_CONFIG_ALARM_STATE_ATTR_BATTERY_SOURCE_LSHIFT;
        }
        else if (attr_set == ZB_ZCL_POWER_CONFIG_BATTERY_SOURCE_2_ATTRIBUTE_SET)
        {
          attr_val = (zb_uint32_t)val << ZB_ZCL_POWER_CONFIG_ALARM_STATE_ATTR_BATTERY_SOURCE_2_LSHIFT;
        }
        else if (attr_set == ZB_ZCL_POWER_CONFIG_BATTERY_SOURCE_3_ATTRIBUTE_SET)
        {
          attr_val = (zb_uint32_t)val << ZB_ZCL_POWER_CONFIG_ALARM_STATE_ATTR_BATTERY_SOURCE_3_LSHIFT;
        }
        ZB_ZCL_SET_DIRECTLY_ATTR_VAL32(attr_desc, attr_val);
      }
    }
    
    /* performs comparison taking into account hysteresis. Returns TRUE
     * if voltage value goes lower then threshold */
    zb_bool_t zcl_pwr_value_under_threshold(zb_uint8_t voltage, zb_uint8_t threshold)
    {
      zb_bool_t ret = ZB_FALSE;
    
      TRACE_MSG(TRACE_ZCL1, "> zcl_pwr_value_under_threshold voltage %hd threshold %hd",
                (FMT__H_H, voltage, threshold));
    
      /* TODO: implement comparison taking into account hysteresis */
      if ((voltage < threshold) && (threshold != 0xff))
      {
        ret = ZB_TRUE;
      }
    
      TRACE_MSG(TRACE_ZCL1, "< zcl_pwr_value_under_threshold ret %hd",
                (FMT__H, ret));
      return ret;
    }
    
    void send_alarm(zb_uint8_t param)
    {
      zb_bool_t ret = ZB_TRUE;
      zb_uint16_t addr = 0;
      zb_uint16_t *user_param = ZB_BUF_GET_PARAM(param, zb_uint16_t);
      zb_uint8_t ep = *user_param;
      zb_uint8_t alarm_code = *user_param >> 8;
    
      /* AN: For Mains Voltage it needs to check current value just before alarm sending one more time.
       * Maybe Voltage has come back to the [Min Threshold; Max Threshold] range
       */
      if ((alarm_code == ZB_ZCL_POWER_CONFIG_MAINS_VOLTAGE_MIN_THRESHOLD_ALARM_CODE) ||
          (alarm_code == ZB_ZCL_POWER_CONFIG_MAINS_VOLTAGE_MAX_THRESHOLD_ALARM_CODE))
      {
        zb_zcl_attr_t *attr_desc;
        zb_uint16_t val,threshold;
        attr_desc = zb_zcl_get_attr_desc_a(
          ep,
          ZB_ZCL_CLUSTER_ID_POWER_CONFIG,
          ZB_ZCL_CLUSTER_SERVER_ROLE,
          ZB_ZCL_ATTR_POWER_CONFIG_MAINS_VOLTAGE_ID);
        val = ZB_ZCL_GET_ATTRIBUTE_VAL_16(attr_desc);
        attr_desc = zb_zcl_get_attr_desc_a(
          ep,
          ZB_ZCL_CLUSTER_ID_POWER_CONFIG,
          ZB_ZCL_CLUSTER_SERVER_ROLE,
          (alarm_code == ZB_ZCL_POWER_CONFIG_MAINS_VOLTAGE_MIN_THRESHOLD_ALARM_CODE) ?
          ZB_ZCL_ATTR_POWER_CONFIG_MAINS_VOLTAGE_MIN_THRESHOLD :
          ZB_ZCL_ATTR_POWER_CONFIG_MAINS_VOLTAGE_MAX_THRESHOLD);
        threshold = ZB_ZCL_GET_ATTRIBUTE_VAL_16(attr_desc);
        if (((alarm_code == ZB_ZCL_POWER_CONFIG_MAINS_VOLTAGE_MIN_THRESHOLD_ALARM_CODE) && (val >= threshold)) ||
            ((alarm_code == ZB_ZCL_POWER_CONFIG_MAINS_VOLTAGE_MAX_THRESHOLD_ALARM_CODE) && (val <= threshold)) ||
            (threshold == ZB_ZCL_POWER_CONFIG_THRESHOLD_ALARM_OMISSION_VALUE))
        {
          ret = ZB_FALSE;
        }
      }
    
      if (ret)
      {
        ZB_ZCL_ALARMS_SEND_ALARM_RES(
          param,
          addr,
          ZB_APS_ADDR_MODE_DST_ADDR_ENDP_NOT_PRESENT,
          0,
          ep,
          ZB_AF_HA_PROFILE_ID,
          NULL, alarm_code, ZB_ZCL_CLUSTER_ID_POWER_CONFIG);
      }
      else
      {
        zb_buf_free(param);
      }
    }
    
    void get_send_alarm_buf(zb_uint8_t param, zb_uint16_t user_param)
    {
      if (!param)
      {
        zb_buf_get_out_delayed_ext(get_send_alarm_buf, user_param, 0);
      }
      else
      {
        zb_uint16_t dwell_period = 0;
        zb_uint16_t *u_param = ZB_BUF_GET_PARAM(param, zb_uint16_t);
        zb_zcl_attr_t *attr_desc = NULL;
        *u_param = user_param;
        attr_desc = zb_zcl_get_attr_desc_a(
          (zb_uint8_t)user_param,
          ZB_ZCL_CLUSTER_ID_POWER_CONFIG,
          ZB_ZCL_CLUSTER_SERVER_ROLE,
          ZB_ZCL_ATTR_POWER_CONFIG_MAINS_DWELL_TRIP_POINT);
        if (attr_desc && ((user_param >> 8 == ZB_ZCL_POWER_CONFIG_MAINS_VOLTAGE_MIN_THRESHOLD_ALARM_CODE) ||
                          (user_param >> 8 == ZB_ZCL_POWER_CONFIG_MAINS_VOLTAGE_MAX_THRESHOLD_ALARM_CODE)))
        {
          dwell_period = ZB_ZCL_GET_ATTRIBUTE_VAL_16(attr_desc);
        }
        zb_schedule_alarm(send_alarm, param, ZB_TIME_ONE_SECOND*dwell_period);
      }
    }
    
    void zcl_pwr_cfg_check_mains_voltage(zb_uint8_t ep, zb_uint16_t val)
    {
      zb_bool_t ret = ZB_FALSE;
      zb_zcl_attr_t *attr_desc;
      zb_zcl_attr_t *attr_desc_mask;
      zb_uint16_t threshold;
    
      attr_desc_mask = zb_zcl_get_attr_desc_a(
        ep,
        ZB_ZCL_CLUSTER_ID_POWER_CONFIG,
        ZB_ZCL_CLUSTER_SERVER_ROLE,
        ZB_ZCL_ATTR_POWER_CONFIG_MAINS_ALARM_MASK_ID);
    
      TRACE_MSG(TRACE_ZCL1, "> zcl_pwr_cfg_check_mains_voltage_value ep %hd, val %d", (FMT__H_D, ep, val));
    
      if (attr_desc_mask)
      {
        attr_desc = zb_zcl_get_attr_desc_a(
          ep,
          ZB_ZCL_CLUSTER_ID_POWER_CONFIG,
          ZB_ZCL_CLUSTER_SERVER_ROLE,
          ZB_ZCL_ATTR_POWER_CONFIG_MAINS_VOLTAGE_MIN_THRESHOLD);
        if (attr_desc)
        {
          threshold = ZB_ZCL_GET_ATTRIBUTE_VAL_16(attr_desc);
          if (val < threshold && threshold != ZB_ZCL_POWER_CONFIG_THRESHOLD_ALARM_OMISSION_VALUE)
          {
            ZB_ZCL_SET_DIRECTLY_ATTR_VAL8(attr_desc_mask, ZB_ZCL_POWER_CONFIG_MAINS_VOLTAGE_ALARM_CODE_MIN_THRESHOLD);
            ret = ZB_TRUE;
          }
        }
        if (!ret)
        {
          attr_desc = zb_zcl_get_attr_desc_a(
            ep,
            ZB_ZCL_CLUSTER_ID_POWER_CONFIG,
            ZB_ZCL_CLUSTER_SERVER_ROLE,
            ZB_ZCL_ATTR_POWER_CONFIG_MAINS_VOLTAGE_MAX_THRESHOLD);
          if (attr_desc)
          {
            threshold = ZB_ZCL_GET_ATTRIBUTE_VAL_16(attr_desc);
            if (val > threshold && threshold != ZB_ZCL_POWER_CONFIG_THRESHOLD_ALARM_OMISSION_VALUE)
            {
              ZB_ZCL_SET_DIRECTLY_ATTR_VAL8(attr_desc_mask, ZB_ZCL_POWER_CONFIG_MAINS_VOLTAGE_ALARM_CODE_MAX_THRESHOLD);
              ret = ZB_TRUE;
            }
          }
        }
        if (ret)
        {
          zb_af_endpoint_desc_t* endpoint_desc = zb_af_get_endpoint_desc(ep);
          zb_zcl_cluster_desc_t *cluster_desc = get_cluster_desc(endpoint_desc, ZB_ZCL_CLUSTER_ID_ALARMS, ZB_ZCL_CLUSTER_SERVER_ROLE);
          if (cluster_desc)
          {
            zb_uint8_t alarm_mask = ZB_ZCL_GET_ATTRIBUTE_VAL_8(attr_desc_mask);
            zb_uint16_t u_param = ( ((alarm_mask & ZB_ZCL_POWER_CONFIG_MAINS_VOLTAGE_ALARM_CODE_MAX_THRESHOLD) ?
                                                    ZB_ZCL_POWER_CONFIG_MAINS_VOLTAGE_ALARM_CODE_MAX_THRESHOLD :
                                                    ZB_ZCL_POWER_CONFIG_MAINS_VOLTAGE_ALARM_CODE_MIN_THRESHOLD)<< 8) | ep;
            zb_buf_get_out_delayed_ext(get_send_alarm_buf, u_param, 0);
           }
        }
      }
    }
      /* HA version of Power Configuration declares 3 sets of
       * BatteryInformation and BatterySettings attr sets. This function
       * is used to check new BATTERY_VOLTAGE attribute or BATTERY_PERCENTAGE_REMAINING
       * value in attribute set defined by attr_set value and set BatteryAlarmState
       * if any alarm appears (BatteryVoltageMinThreshold or other threshold check)
       */
    void zcl_pwr_cfg_check_battery_voltage_or_percentage_value(zb_uint8_t ep, zb_uint8_t val,
                                                                    zb_uint16_t attr_set, zb_bool_t is_percentage)
    {
      zb_bool_t ret = ZB_FALSE;
      zb_zcl_attr_t *attr_desc;
      zb_zcl_attr_t *attr_desc_state;
      zb_zcl_attr_t *attr_desc_mask;
      zb_uint8_t threshold;
    
      TRACE_MSG(TRACE_ZCL1, "> zcl_pwr_cfg_check_val_value ep %hd, val %hd, attr_set %d",
                (FMT__H_H_D, ep, val, attr_set));
    
      attr_desc_state = zb_zcl_get_attr_desc_a(
        ep,
        ZB_ZCL_CLUSTER_ID_POWER_CONFIG,
        ZB_ZCL_CLUSTER_SERVER_ROLE,
        (zb_uint16_t)(attr_set+ZB_ZCL_ATTR_POWER_CONFIG_BATTERY_ALARM_STATE_ID));
    
      attr_desc_mask = zb_zcl_get_attr_desc_a(
        ep,
        ZB_ZCL_CLUSTER_ID_POWER_CONFIG,
        ZB_ZCL_CLUSTER_SERVER_ROLE,
        (zb_uint16_t)(attr_set+ZB_ZCL_ATTR_POWER_CONFIG_BATTERY_ALARM_MASK_ID));
    
      if (attr_desc_state)
      {
        /*** Check Min threshold value ***/
        attr_desc = zb_zcl_get_attr_desc_a(
          ep,
          ZB_ZCL_CLUSTER_ID_POWER_CONFIG,
          ZB_ZCL_CLUSTER_SERVER_ROLE,
          (is_percentage) ?
          (zb_uint16_t)(attr_set+ZB_ZCL_ATTR_POWER_CONFIG_BATTERY_PERCENTAGE_MIN_THRESHOLD_ID):
          (zb_uint16_t)(attr_set+ZB_ZCL_ATTR_POWER_CONFIG_BATTERY_VOLTAGE_MIN_THRESHOLD_ID));
        if(attr_desc)
        {
          threshold = ZB_ZCL_GET_ATTRIBUTE_VAL_8(attr_desc);
          if (zcl_pwr_value_under_threshold(val, threshold))
          {
            fill_alarm_state_bitmap(ep, ZB_ZCL_POWER_CONFIG_BATTERY_ALARM_STATE_SOURCE1_MIN_THRESHOLD, attr_desc_mask, attr_set, is_percentage);
            if (attr_desc_mask)
            {
              fill_alarm_mask_bitmap(ep, ZB_ZCL_POWER_CONFIG_BATTERY_ALARM_STATE_SOURCE1_MIN_THRESHOLD, attr_desc_mask, attr_set, is_percentage);
            }
            ret = ZB_TRUE;
          }
        }
    
        /*** Check Threshold1 value ***/
        if (!ret)
        {
          attr_desc = zb_zcl_get_attr_desc_a(
            ep,
            ZB_ZCL_CLUSTER_ID_POWER_CONFIG,
            ZB_ZCL_CLUSTER_SERVER_ROLE,
            (is_percentage) ?
            (zb_uint16_t)(attr_set+ZB_ZCL_ATTR_POWER_CONFIG_BATTERY_PERCENTAGE_THRESHOLD1_ID):
            (zb_uint16_t)(attr_set+ZB_ZCL_ATTR_POWER_CONFIG_BATTERY_VOLTAGE_THRESHOLD1_ID));
          if(attr_desc)
          {
            threshold = ZB_ZCL_GET_ATTRIBUTE_VAL_8(attr_desc);
            if (zcl_pwr_value_under_threshold(val, threshold))
            {
              fill_alarm_state_bitmap(ep, ZB_ZCL_POWER_CONFIG_BATTERY_ALARM_STATE_SOURCE1_VOLTAGE1, attr_desc_mask, attr_set, is_percentage);
              if (attr_desc_mask)
              {
                fill_alarm_mask_bitmap(ep, ZB_ZCL_POWER_CONFIG_BATTERY_ALARM_STATE_SOURCE1_VOLTAGE1, attr_desc_mask, attr_set, is_percentage);
              }
              ret = ZB_TRUE;
            }
          }
        }
        /*** Check Threshold2 value ***/
        if (!ret)
        {
          attr_desc = zb_zcl_get_attr_desc_a(
            ep,
            ZB_ZCL_CLUSTER_ID_POWER_CONFIG,
            ZB_ZCL_CLUSTER_SERVER_ROLE,
            (is_percentage) ?
            (zb_uint16_t)(attr_set+ZB_ZCL_ATTR_POWER_CONFIG_BATTERY_PERCENTAGE_THRESHOLD2_ID):
            (zb_uint16_t)(attr_set+ZB_ZCL_ATTR_POWER_CONFIG_BATTERY_VOLTAGE_THRESHOLD2_ID));
          if(attr_desc)
          {
            threshold = ZB_ZCL_GET_ATTRIBUTE_VAL_8(attr_desc);
            if (zcl_pwr_value_under_threshold(val, threshold))
            {
              fill_alarm_state_bitmap(ep, ZB_ZCL_POWER_CONFIG_BATTERY_ALARM_STATE_SOURCE1_VOLTAGE2, attr_desc_mask, attr_set, is_percentage);
              if (attr_desc_mask)
              {
                fill_alarm_mask_bitmap(ep, ZB_ZCL_POWER_CONFIG_BATTERY_ALARM_STATE_SOURCE1_VOLTAGE2, attr_desc_mask, attr_set, is_percentage);
              }
              ret = ZB_TRUE;
            }
          }
        }
    
        /*** Check Threshold3 value ***/
        if (!ret)
        {
          attr_desc = zb_zcl_get_attr_desc_a(
            ep,
            ZB_ZCL_CLUSTER_ID_POWER_CONFIG,
            ZB_ZCL_CLUSTER_SERVER_ROLE,
            (is_percentage) ?
            (zb_uint16_t)(attr_set+ZB_ZCL_ATTR_POWER_CONFIG_BATTERY_PERCENTAGE_THRESHOLD3_ID):
            (zb_uint16_t)(attr_set+ZB_ZCL_ATTR_POWER_CONFIG_BATTERY_VOLTAGE_THRESHOLD3_ID));
          if(attr_desc)
          {
            threshold = ZB_ZCL_GET_ATTRIBUTE_VAL_8(attr_desc);
    
            if (zcl_pwr_value_under_threshold(val, threshold))
            {
              fill_alarm_state_bitmap(ep, ZB_ZCL_POWER_CONFIG_BATTERY_ALARM_STATE_SOURCE1_VOLTAGE3, attr_desc_mask, attr_set, is_percentage);
              if (attr_desc_mask)
              {
                fill_alarm_mask_bitmap(ep, ZB_ZCL_POWER_CONFIG_BATTERY_ALARM_STATE_SOURCE1_VOLTAGE3, attr_desc_mask, attr_set, is_percentage);
              }
              ret = ZB_TRUE;
            }
          }
        }
        if (ret)
        {
          if (attr_desc_mask)
          {
            zb_af_endpoint_desc_t* endpoint_desc = zb_af_get_endpoint_desc(ep);
            zb_zcl_cluster_desc_t *cluster_desc = get_cluster_desc(endpoint_desc, ZB_ZCL_CLUSTER_ID_ALARMS, ZB_ZCL_CLUSTER_SERVER_ROLE);
            if (cluster_desc)
            {
              zb_uint8_t alarm_mask = ZB_ZCL_GET_ATTRIBUTE_VAL_8(attr_desc_mask);
              /* AN: Looks like, that we can send only one "the most significant" alarm.
               * ZCL spec strongly requires (p. 3.3.2.2.4.8 BatteryVoltageThreshold 1-3 Attributes)
               * that BatteryVoltageThresholdN+1 has to be higher than the BatteryVoltageThresholdN.
               *
               * ms-alarm - subtrahend with range [0;4]. It is needed for Alarm code "threshold" nibble (10*[0,1,2] + 4 - ms_alarm)
               */
              zb_uint8_t ms_alarm = (alarm_mask & ZB_ZCL_POWER_CONFIG_BATTERY_ALARM_STATE_SOURCE1_MIN_THRESHOLD) ? 4 : (
                                    (alarm_mask & ZB_ZCL_POWER_CONFIG_BATTERY_ALARM_STATE_SOURCE1_VOLTAGE1) ? 3 : (
                                    (alarm_mask & ZB_ZCL_POWER_CONFIG_BATTERY_ALARM_STATE_SOURCE1_VOLTAGE2) ? 2 : (
                                    (alarm_mask & ZB_ZCL_POWER_CONFIG_BATTERY_ALARM_STATE_SOURCE1_VOLTAGE3) ? 1 : 0 )));
              zb_uint16_t u_param = (((attr_set+ZB_ZCL_POWER_CONFIG_BATTERY_ALARM_CODES_ADDEND)/2 + (4-ms_alarm)) << 8) | ep;
              zb_buf_get_out_delayed_ext(get_send_alarm_buf, u_param, 0);
            }
          }
        }
    
      }
      TRACE_MSG(TRACE_ZCL1, "< zcl_pwr_cfg_check_val_value ret %hd", (FMT__H, ret));
    }
    
    void zb_zcl_power_config_write_attr_hook_server(zb_uint8_t endpoint, zb_uint16_t attr_id, zb_uint8_t *new_value, zb_uint16_t manuf_code)
    {
      TRACE_MSG(TRACE_ZCL1, "> zb_zcl_power_config_write_attr_hook endpoint %hx attr_id 0x%x, manuf_code 0x%x",
                (FMT__H_D_D, endpoint, attr_id, manuf_code));
    
      ZVUNUSED(manuf_code);
    
      if (attr_id == ZB_ZCL_ATTR_POWER_CONFIG_MAINS_VOLTAGE_ID)
      {
        zb_uint16_t new_value_16 = ZB_ZCL_ATTR_GET16(new_value);
        zcl_pwr_cfg_check_mains_voltage(endpoint, new_value_16);
      }
      if (attr_id == ZB_ZCL_ATTR_POWER_CONFIG_BATTERY_VOLTAGE_ID ||
          attr_id == ZB_ZCL_ATTR_POWER_CONFIG_BATTERY2_VOLTAGE_ID ||
          attr_id == ZB_ZCL_ATTR_POWER_CONFIG_BATTERY3_VOLTAGE_ID)
      {
        zcl_pwr_cfg_check_battery_voltage_or_percentage_value(endpoint, *new_value,
                                                              attr_id-ZB_ZCL_POWER_CONFIG_BATTERY_VOLTAGE_ATTR_SUBTRAHEND, ZB_FALSE);
      }
      else if (attr_id == ZB_ZCL_ATTR_POWER_CONFIG_BATTERY_PERCENTAGE_REMAINING_ID ||
               attr_id == ZB_ZCL_ATTR_POWER_CONFIG_BATTERY2_PERCENTAGE_REMAINING_ID ||
               attr_id == ZB_ZCL_ATTR_POWER_CONFIG_BATTERY3_PERCENTAGE_REMAINING_ID)
      {
        zcl_pwr_cfg_check_battery_voltage_or_percentage_value(endpoint, *new_value,
                                                              attr_id-ZB_ZCL_POWER_CONFIG_BATTERY_PERCENTAGE_REMAINING_ATTR_SUBTRAHEND, ZB_TRUE);
      }
    
      TRACE_MSG(TRACE_ZCL1, "< zb_zcl_power_config_write_attr_hook", (FMT__0));
    }
    
    /**
     *  @} internal
    */
    
    #endif /* ZB_ZCL_SUPPORT_CLUSTER_POWER_CONFIG */

    Kind regards,
    Andreas

Related