huesmith 0.1.0

Hue-compatible Zigbee light library for ESP32-C6/H2 (ESP-IDF)
use super::ffi;
use huesmith_core::hue::device::HueDeviceType;

fn reportable_change(attr_type: u8) -> ffi::esp_zb_zcl_attr_var_u {
    match attr_type {
        ffi::ZCL_ATTR_TYPE_BOOL | ffi::ZCL_ATTR_TYPE_U8 | ffi::ZCL_ATTR_TYPE_ENUM8 => {
            ffi::esp_zb_zcl_attr_var_u::from_u8(1)
        }
        ffi::ZCL_ATTR_TYPE_U16 => ffi::esp_zb_zcl_attr_var_u::from_u16(1),
        _ => ffi::esp_zb_zcl_attr_var_u::zero(),
    }
}

/// Configure attribute reporting for a cluster attribute.
/// The Hue Bridge typically configures reporting itself, but we set sane defaults.
pub fn configure_reporting(
    endpoint: u8,
    cluster_id: u16,
    attr_id: u16,
    attr_type: u8,
    min_interval: u16,
    max_interval: u16,
) {
    let attr_location = ffi::esp_zb_zcl_attr_location_info_t {
        endpoint_id: endpoint,
        cluster_id,
        cluster_role: ffi::ZB_ZCL_CLUSTER_SERVER_ROLE,
        manuf_code: ffi::ZB_ZCL_ATTR_NON_MANUFACTURER_SPECIFIC,
        attr_id,
    };

    let mut info = ffi::esp_zb_zcl_reporting_info_t {
        direction: ffi::ZB_ZCL_REPORT_DIRECTION_SEND,
        ep: endpoint,
        cluster_id,
        cluster_role: ffi::ZB_ZCL_CLUSTER_SERVER_ROLE,
        attr_id,
        flags: 0,
        run_time: 0,
        u: ffi::esp_zb_zcl_reporting_info_u {
            send_info: ffi::esp_zb_zcl_reporting_send_info_t {
                min_interval,
                max_interval,
                delta: reportable_change(attr_type),
                reported_value: ffi::esp_zb_zcl_attr_var_u::zero(),
                def_min_interval: min_interval,
                def_max_interval: max_interval,
            },
        },
        dst: ffi::esp_zb_zcl_reporting_dst_t {
            short_addr: 0,
            endpoint: 0,
            profile_id: ffi::PROFILE_HA,
        },
        manuf_code: ffi::ZB_ZCL_ATTR_NON_MANUFACTURER_SPECIFIC,
    };

    let ret = unsafe { ffi::esp_zb_zcl_update_reporting_info(&mut info) };
    if ret != 0 {
        log::warn!(
            "Failed to configure reporting for cluster 0x{:04X} attr 0x{:04X}: {}",
            cluster_id,
            attr_id,
            ret,
        );
        return;
    }

    let ret = unsafe { ffi::esp_zb_zcl_start_attr_reporting(attr_location) };
    if ret != 0 {
        log::warn!(
            "Failed to start reporting for cluster 0x{:04X} attr 0x{:04X}: {}",
            cluster_id,
            attr_id,
            ret,
        );
    }
}

/// Set up default reporting for a Hue light endpoint.
///
/// Level and Color reporting are only configured when the device type actually
/// registers those clusters; an on/off-only light has neither, so configuring
/// them would just fail.
pub fn setup_default_reporting(endpoint: u8, device_type: HueDeviceType) {
    let has_level_control = device_type.has_level_control();
    let has_color_control = device_type.has_color_control();

    // On/Off state
    configure_reporting(
        endpoint,
        ffi::CLUSTER_ON_OFF,
        ffi::ATTR_ON_OFF,
        ffi::ZCL_ATTR_TYPE_BOOL,
        1,   // min 1 second
        300, // max 5 minutes
    );

    // Level — only present on dimmable and above.
    if has_level_control {
        configure_reporting(
            endpoint,
            ffi::CLUSTER_LEVEL_CONTROL,
            ffi::ATTR_LEVEL_CURRENT_LEVEL,
            ffi::ZCL_ATTR_TYPE_U8,
            1,
            300,
        );
    }

    if !has_color_control {
        return;
    }

    // Color X
    configure_reporting(
        endpoint,
        ffi::CLUSTER_COLOR_CONTROL,
        ffi::ATTR_COLOR_CURRENT_X,
        ffi::ZCL_ATTR_TYPE_U16,
        1,
        300,
    );

    // Color Y
    configure_reporting(
        endpoint,
        ffi::CLUSTER_COLOR_CONTROL,
        ffi::ATTR_COLOR_CURRENT_Y,
        ffi::ZCL_ATTR_TYPE_U16,
        1,
        300,
    );

    // Color Temperature
    configure_reporting(
        endpoint,
        ffi::CLUSTER_COLOR_CONTROL,
        ffi::ATTR_COLOR_TEMPERATURE_MIREDS,
        ffi::ZCL_ATTR_TYPE_U16,
        1,
        300,
    );
}