use super::ffi;
use huesmith_core::hue::device::{
COLOR_CAP_COLOR_LOOP, COLOR_CAP_COLOR_TEMP, COLOR_CAP_ENHANCED_HUE,
};
use huesmith_core::hue::identity::DeviceIdentity;
pub fn create_basic_cluster(identity: &DeviceIdentity) -> *mut ffi::esp_zb_attribute_list_t {
let cfg = ffi::esp_zb_basic_cluster_cfg_t {
zcl_version: 0x03,
power_source: ffi::POWER_SOURCE_MAINS,
};
let cluster = unsafe { ffi::esp_zb_basic_cluster_create(&cfg) };
let manuf = zcl_string(identity.manufacturer_name, ZCL_BASIC_NAME_MAX);
let model = zcl_string(identity.model_identifier, ZCL_BASIC_NAME_MAX);
let date = zcl_string(identity.date_code, ZCL_BASIC_CODE_MAX);
let sw_build = zcl_string(identity.sw_build_id, ZCL_BASIC_CODE_MAX);
let hw_ver: &'static mut u8 = Box::leak(Box::new(identity.hw_version));
unsafe {
ffi::esp_zb_basic_cluster_add_attr(
cluster,
ffi::ATTR_BASIC_MANUFACTURER_NAME,
manuf.as_ptr() as *mut _,
);
ffi::esp_zb_basic_cluster_add_attr(
cluster,
ffi::ATTR_BASIC_MODEL_IDENTIFIER,
model.as_ptr() as *mut _,
);
ffi::esp_zb_basic_cluster_add_attr(
cluster,
ffi::ATTR_BASIC_DATE_CODE,
date.as_ptr() as *mut _,
);
ffi::esp_zb_basic_cluster_add_attr(
cluster,
ffi::ATTR_BASIC_SW_BUILD_ID,
sw_build.as_ptr() as *mut _,
);
let r1 = ffi::esp_zb_cluster_update_attr(
cluster,
ffi::ATTR_BASIC_MANUFACTURER_NAME,
manuf.as_ptr() as *mut _,
);
let r2 = ffi::esp_zb_cluster_update_attr(
cluster,
ffi::ATTR_BASIC_MODEL_IDENTIFIER,
model.as_ptr() as *mut _,
);
let r3 = ffi::esp_zb_cluster_add_attr(
cluster,
ffi::CLUSTER_BASIC,
ffi::ATTR_BASIC_HW_VERSION,
ffi::ZCL_ATTR_TYPE_U8,
ffi::ZB_ZCL_ATTR_ACCESS_READ_ONLY,
hw_ver as *mut u8 as *mut core::ffi::c_void,
);
log::info!(
"Basic cluster: {} {} (hw v{}) [manuf/model update: {},{}; hw add: {}]",
identity.manufacturer_name,
identity.model_identifier,
identity.hw_version,
r1,
r2,
r3,
);
}
cluster
}
pub fn create_identify_cluster() -> *mut ffi::esp_zb_attribute_list_t {
let cfg = ffi::esp_zb_identify_cluster_cfg_t { identify_time: 0 };
unsafe { ffi::esp_zb_identify_cluster_create(&cfg) }
}
pub fn create_groups_cluster() -> *mut ffi::esp_zb_attribute_list_t {
let cfg = ffi::esp_zb_groups_cluster_cfg_t {
groups_name_support_id: 0,
};
unsafe { ffi::esp_zb_groups_cluster_create(&cfg) }
}
pub fn create_scenes_cluster() -> *mut ffi::esp_zb_attribute_list_t {
let cfg = ffi::esp_zb_scenes_cluster_cfg_t {
scenes_count: 0,
current_scene: 0,
current_group: 0,
scene_valid: false,
name_support: 0,
};
unsafe { ffi::esp_zb_scenes_cluster_create(&cfg) }
}
pub fn create_on_off_cluster(initial_on: bool) -> *mut ffi::esp_zb_attribute_list_t {
let cfg = ffi::esp_zb_on_off_cluster_cfg_t { on_off: initial_on };
let cluster = unsafe { ffi::esp_zb_on_off_cluster_create(&cfg) };
let global_scene = Box::leak(Box::new(true));
let on_time = Box::leak(Box::new(0u16));
let off_wait = Box::leak(Box::new(0u16));
let startup = Box::leak(Box::new(if initial_on { 0x01u8 } else { 0x00u8 }));
unsafe {
ffi::esp_zb_cluster_add_attr(
cluster,
ffi::CLUSTER_ON_OFF,
ffi::ATTR_ON_OFF_GLOBAL_SCENE_CONTROL,
ffi::ZCL_ATTR_TYPE_BOOL,
ffi::ZB_ZCL_ATTR_ACCESS_READ_ONLY,
global_scene as *mut bool as *mut _,
);
ffi::esp_zb_cluster_add_attr(
cluster,
ffi::CLUSTER_ON_OFF,
ffi::ATTR_ON_OFF_ON_TIME,
ffi::ZCL_ATTR_TYPE_U16,
ffi::ZB_ZCL_ATTR_ACCESS_READ_WRITE,
on_time as *mut u16 as *mut _,
);
ffi::esp_zb_cluster_add_attr(
cluster,
ffi::CLUSTER_ON_OFF,
ffi::ATTR_ON_OFF_OFF_WAIT_TIME,
ffi::ZCL_ATTR_TYPE_U16,
ffi::ZB_ZCL_ATTR_ACCESS_READ_WRITE,
off_wait as *mut u16 as *mut _,
);
ffi::esp_zb_cluster_add_attr(
cluster,
ffi::CLUSTER_ON_OFF,
ffi::ATTR_ON_OFF_START_UP_ON_OFF,
ffi::ZCL_ATTR_TYPE_ENUM8,
ffi::ZB_ZCL_ATTR_ACCESS_READ_WRITE,
startup as *mut u8 as *mut _,
);
}
cluster
}
pub fn create_level_cluster(initial_level: u8) -> *mut ffi::esp_zb_attribute_list_t {
let cfg = ffi::esp_zb_level_cluster_cfg_t {
current_level: initial_level,
};
let cluster = unsafe { ffi::esp_zb_level_cluster_create(&cfg) };
let startup_level = Box::leak(Box::new(initial_level));
let options = Box::leak(Box::new(0u8));
unsafe {
ffi::esp_zb_cluster_add_attr(
cluster,
ffi::CLUSTER_LEVEL_CONTROL,
ffi::ATTR_LEVEL_START_UP_CURRENT_LEVEL,
ffi::ZCL_ATTR_TYPE_U8,
ffi::ZB_ZCL_ATTR_ACCESS_READ_WRITE,
startup_level as *mut u8 as *mut _,
);
ffi::esp_zb_cluster_add_attr(
cluster,
ffi::CLUSTER_LEVEL_CONTROL,
ffi::ATTR_LEVEL_OPTIONS,
ffi::ZCL_ATTR_TYPE_U8,
ffi::ZB_ZCL_ATTR_ACCESS_READ_WRITE,
options as *mut u8 as *mut _,
);
}
cluster
}
pub fn create_color_control_cluster(
capabilities: u16,
min_mireds: u16,
max_mireds: u16,
) -> *mut ffi::esp_zb_attribute_list_t {
let cfg = ffi::esp_zb_color_control_cluster_cfg_t {
current_x: 20495,
current_y: 21561,
color_mode: ffi::COLOR_MODE_COLOR_TEMP,
options: 0,
enhanced_color_mode: ffi::ENHANCED_COLOR_MODE_COLOR_TEMP,
color_capabilities: capabilities,
};
let cluster = unsafe { ffi::esp_zb_color_control_cluster_create(&cfg) };
let rw = ffi::ZB_ZCL_ATTR_ACCESS_READ_WRITE;
let ro = ffi::ZB_ZCL_ATTR_ACCESS_READ_ONLY;
let ror = ro | ffi::ZB_ZCL_ATTR_ACCESS_REPORTING;
let u8_t = ffi::ZCL_ATTR_TYPE_U8;
let u16_t = ffi::ZCL_ATTR_TYPE_U16;
let add = |attr_id: u16, attr_type: u8, access: u8, ptr: *mut u8| unsafe {
ffi::esp_zb_cluster_add_attr(
cluster,
ffi::CLUSTER_COLOR_CONTROL,
attr_id,
attr_type,
access,
ptr as *mut core::ffi::c_void,
);
};
if capabilities & COLOR_CAP_COLOR_TEMP != 0 {
let color_temp = Box::leak(Box::new(370u16));
let min_m = Box::leak(Box::new(min_mireds));
let max_m = Box::leak(Box::new(max_mireds));
let startup_ct = Box::leak(Box::new(0xFFFFu16));
let couple_ct_min = Box::leak(Box::new(min_mireds));
add(
ffi::ATTR_COLOR_TEMPERATURE_MIREDS,
u16_t,
ror,
color_temp as *mut u16 as *mut u8,
);
add(
ffi::ATTR_COLOR_TEMP_PHYSICAL_MIN_MIREDS,
u16_t,
ro,
min_m as *mut u16 as *mut u8,
);
add(
ffi::ATTR_COLOR_TEMP_PHYSICAL_MAX_MIREDS,
u16_t,
ro,
max_m as *mut u16 as *mut u8,
);
add(
ffi::ATTR_COLOR_START_UP_COLOR_TEMPERATURE_MIREDS,
u16_t,
rw,
startup_ct as *mut u16 as *mut u8,
);
add(
ffi::ATTR_COLOR_COUPLE_COLOR_TEMP_TO_LEVEL_MIN_MIREDS,
u16_t,
ro,
couple_ct_min as *mut u16 as *mut u8,
);
}
if capabilities & COLOR_CAP_ENHANCED_HUE != 0 {
let enhanced_hue = Box::leak(Box::new(0u16));
let loop_start_hue = Box::leak(Box::new(0u16));
let loop_stored_hue = Box::leak(Box::new(0u16));
add(
ffi::ATTR_COLOR_ENHANCED_CURRENT_HUE,
u16_t,
ror,
enhanced_hue as *mut u16 as *mut u8,
);
add(
ffi::ATTR_COLOR_LOOP_START_ENHANCED_HUE,
u16_t,
ro,
loop_start_hue as *mut u16 as *mut u8,
);
add(
ffi::ATTR_COLOR_LOOP_STORED_ENHANCED_HUE,
u16_t,
ro,
loop_stored_hue as *mut u16 as *mut u8,
);
}
if capabilities & COLOR_CAP_COLOR_LOOP != 0 {
let loop_active = Box::leak(Box::new(0u8));
let loop_direction = Box::leak(Box::new(0u8));
let loop_time = Box::leak(Box::new(25u16));
add(
ffi::ATTR_COLOR_LOOP_ACTIVE,
u8_t,
ror,
loop_active as *mut u8,
);
add(
ffi::ATTR_COLOR_LOOP_DIRECTION,
u8_t,
rw,
loop_direction as *mut u8,
);
add(
ffi::ATTR_COLOR_LOOP_TIME,
u16_t,
rw,
loop_time as *mut u16 as *mut u8,
);
}
cluster
}
const ZCL_BASIC_NAME_MAX: usize = 32;
const ZCL_BASIC_CODE_MAX: usize = 16;
fn zcl_string(s: &'static str, max: usize) -> &'static [u8] {
let bytes = s.as_bytes();
if bytes.len() > max {
log::warn!("Basic cluster string {s:?} exceeds the ZCL {max}-byte limit; truncating");
}
let len = bytes.len().min(max);
let mut buf = Vec::with_capacity(1 + len);
buf.push(len as u8);
buf.extend_from_slice(&bytes[..len]);
buf.leak()
}