Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom cluster with multiple string attributes #31

Open
Wollwolke opened this issue Feb 16, 2025 · 4 comments
Open

Custom cluster with multiple string attributes #31

Wollwolke opened this issue Feb 16, 2025 · 4 comments

Comments

@Wollwolke
Copy link

Hey, I'm currently working on a ZigBee-based BLE proxy (mainly for use with the Bermuda integration).

So I have created a configuration with a custom cluster containing two string attributes:

zigbee:
  id: "zb"
  endpoints:
    ...
    - device_type: CUSTOM_ATTR
      num: 3
      clusters:
        - id: 0xFFEE
          attributes:
            - attribute_id: 0x1
              access: READ_WRITE
              type: CHAR_STRING
              report: true
              id: zigbee_zigbeeattribute_scanresult
            - attribute_id: 0x2
              access: READ_WRITE
              type: CHAR_STRING
              report: true
              id: zigbee_zigbeeattribute_macaddress
  on_connect:
    then:
      - logger.log: "Connected to network"
      - lambda: |-
          set_local_bt_address();
      - esp32_ble_tracker.start_scan:
          continuous: true

The scanresult attribute is set by a callback (bt_zigbee_tracker_cb) from the ble_tracker, while the macaddress attribute is set using the set_local_bt_address function.

C++ Implementation

void report(int src_endpoint, int cluster_id, int attribute_id)
{
    esp_zb_zcl_report_attr_cmd_t cmd = {
        .address_mode = ESP_ZB_APS_ADDR_MODE_16_ENDP_PRESENT,
        .direction = ESP_ZB_ZCL_CMD_DIRECTION_TO_CLI,
    };
    cmd.zcl_basic_cmd.dst_addr_u.addr_short = 0x0000;
    cmd.zcl_basic_cmd.dst_endpoint = 1;

    cmd.zcl_basic_cmd.src_endpoint = src_endpoint;
    cmd.clusterID = cluster_id;
    cmd.attributeID = attribute_id;
    // cmd.cluster_role = reporting_info.cluster_role;

    esp_zb_zcl_report_attr_cmd_req(&cmd);
}

void bt_zigbee_tracker_cb(esp32_ble_tracker::ESPBTDevice x)
{
    std::string address = x.address_str();
    if (is_address_in_filter(address.c_str()))
    {
        std::stringstream ss;
        ss << address << "|" << x.get_rssi() << "|" << x.get_name();
        auto zb_string = ss.str();
        size_t length = zb_string.length() + 1 + 1; // string + \0 + length byte

        char *heapString = new char[length];
        reinterpret_cast<uint8_t *>(heapString)[0] = zb_string.length();
        strncpy(heapString + 1, zb_string.c_str(), length - 1);
        ESP_LOGD("wolke", heapString);
        zigbee_zigbeeattribute_scanresult->set_attr(heapString);

        if (esp_zb_lock_acquire(20 / portTICK_PERIOD_MS))
        {
            report(3, 0xFFEE, 1);
            esp_zb_lock_release();
        }
    }
}

void set_local_bt_address()
{
    const uint8_t *bd_addr = esp_bt_dev_get_address();
    if (bd_addr != NULL)
    {
        ESP_LOGD("wolke", "Bluetooth MAC Address: %02X:%02X:%02X:%02X:%02X:%02X",
                 bd_addr[0], bd_addr[1], bd_addr[2], bd_addr[3], bd_addr[4], bd_addr[5]);

        size_t length = 17 + 1 + 1; // address length + \0 + length byte
        char *bd_addr_str = new char[length];

        // write length byte and formated address
        reinterpret_cast<uint8_t *>(bd_addr_str)[0] = 17;
        sprintf(bd_addr_str + 1, "%02X:%02X:%02X:%02X:%02X:%02X",
                bd_addr[0], bd_addr[1], bd_addr[2], bd_addr[3], bd_addr[4], bd_addr[5]);
        ESP_LOGD("wolke", "Length: %d\n", (int)bd_addr_str[0]);
        ESP_LOGD("wolke", "Address: %s\n", bd_addr_str + 1);
        zigbee_zigbeeattribute_macaddress->set_attr(bd_addr_str);
        if (esp_zb_lock_acquire(20 / portTICK_PERIOD_MS))
        {
            // report(3, 0xFFEE, 2);
            esp_zb_lock_release();
        }
    }
    else
    {
        ESP_LOGE("wolke", "Bluetooth MAC Address not available");
    }
}

It works fine as long as you do not use both attributes. When I try to use both, the debug logs still show the correct data in the char arrays, but the transmitted values (double-checked with a sniffer) are a mix of both. It looks like the memory region is being reused somehow.
Am I using the set_attr() API incorrectly?

I've also tried implementing a set_attr_array' function that uses the delete[]operator instead ofdelete', but that doesn't help.

@luar123
Copy link
Owner

luar123 commented Feb 17, 2025

String attributes are not really implemented and I never tested those. Can you try to deactivate reporting and just use the coordinator to read them? Just to eliminate one potential source of error.
Your code seem fine to me. However, one issue could be that the default value is an integer 0 instead of an empty string. This gets overwritten by the first set_attr command so it shouldn't be an issue, but if both 0s have the same memory address it could cause an issue. Maybe try to set the default value to some long strings (different for both attributes). This might or might not crash, because the strings are not converted to an char array.

@Wollwolke
Copy link
Author

Wollwolke commented Feb 18, 2025

Setting a long string as the default value results in the same behavior as the default value of 0. Setting it to "1" and "2" (values must be different) actually works, although the actual value is garbage at first until the attributes are set for the first time. I feel like it's more or less just luck 😅
I think the problem is related to the fact that you have to pass the pointer when creating the attribute with esp_zb_cluster_add_attr() and we can't do that correctly at the moment.

Slightly off topic: The method I used to send a report right after setting an attribute actually sends the old value of the attribute (probably because the loop of the attribute hasn't written the value yet). Do you have a clever way to fix this?

@luar123
Copy link
Owner

luar123 commented Feb 18, 2025

Setting a long string as the default value results in the same behavior as the default value of 0. Setting it to "1" and "2" (values must be different) actually works, although the actual value is garbage at first until the attributes are set for the first time. I feel like it's more or less just luck 😅 I think the problem is related to the fact that you have to pass the pointer when creating the attribute with esp_zb_cluster_add_attr() and we can't do that correctly at the moment.

yes that's what I thought. Basically we need to add string support to add_attr().

Slightly off topic: The method I used to send a report right after setting an attribute actually sends the old value of the attribute (probably because the loop of the attribute hasn't written the value yet). Do you have a clever way to fix this?

Ha, yes that could happen. Just don't use the manual reporting :D
Can you configure reporting through z2m instead? Not sure if that works for custom/string attributes.
Otherwise, I thought of adding a report: force option, that would trigger an report after actually setting the attribute.

@Wollwolke
Copy link
Author

Wollwolke commented Feb 19, 2025

yes that's what I thought. Basically we need to add string support to add_attr().

I might look into that if I find a little time this week.

Otherwise, I thought of adding a report: force option, that would trigger an report after actually setting the attribute.

Yeah, even thought about moving the reporting logic to the attributes themselves, so we can report attributes individually.
Actually I have to test wether / how fast the default reporting mechanism reacts to changes in string attributes, maybe I don't need the manual reporting at all 😅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants