Skip to content

feat(zigbee): Add Occupancy sensor type and configuration #11246

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

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
#include "Zigbee.h"

/* Zigbee occupancy sensor configuration */
#define OCCUPANCY_SENSOR_ENDPOINT_NUMBER 10
#define OCCUPANCY_SENSOR_ENDPOINT_NUMBER 1
uint8_t button = BOOT_PIN;
uint8_t sensor_pin = 4;

Expand All @@ -47,9 +47,24 @@ void setup() {
pinMode(button, INPUT_PULLUP);
pinMode(sensor_pin, INPUT);

// Optional: set Zigbee device name and model
// Set Zigbee device name and model
zbOccupancySensor.setManufacturerAndModel("Espressif", "ZigbeeOccupancyPIRSensor");

// Optional: Set sensor type (PIR, Ultrasonic, PIR and Ultrasonic or Physical Contact)
zbOccupancySensor.setSensorType(ZIGBEE_OCCUPANCY_SENSOR_TYPE_PIR, ZIGBEE_OCCUPANCY_SENSOR_BITMAP_PIR);

// Optional: Set occupied to unoccupied delay (if your sensor supports it) to PIR sensor as its set by setSensorType
zbOccupancySensor.setOccupiedToUnoccupiedDelay(ZIGBEE_OCCUPANCY_SENSOR_TYPE_PIR, 1); // 1 second delay

// Optional: Set unoccupied to occupied delay (if your sensor supports it) to PIR sensor as its set by setSensorType
zbOccupancySensor.setUnoccupiedToOccupiedDelay(ZIGBEE_OCCUPANCY_SENSOR_TYPE_PIR, 1); // 1 second delay

// Optional: Set unoccupied to occupied threshold (if your sensor supports it) to PIR sensor as its set by setSensorType
zbOccupancySensor.setUnoccupiedToOccupiedThreshold(ZIGBEE_OCCUPANCY_SENSOR_TYPE_PIR, 1); // 1 movement event threshold

// Optional: Set callback function for occupancy config change
zbOccupancySensor.onOccupancyConfigChange(occupancyConfigChange);

// Add endpoint to Zigbee Core
Zigbee.addEndpoint(&zbOccupancySensor);

Expand Down Expand Up @@ -101,3 +116,9 @@ void loop() {
}
delay(100);
}

// Callback function for occupancy config change
void occupancyConfigChange(ZigbeeOccupancySensorType sensor_type, uint16_t occ_to_unocc_delay, uint16_t unocc_to_occ_delay, uint8_t unocc_to_occ_threshold) {
// Handle sensor configuration here
Serial.printf("Occupancy config change: sensor type: %d, occ to unocc delay: %d, unocc to occ delay: %d, unocc to occ threshold: %d\n", sensor_type, occ_to_unocc_delay, unocc_to_occ_delay, unocc_to_occ_threshold);
}
214 changes: 211 additions & 3 deletions libraries/Zigbee/src/ep/ZigbeeOccupancySensor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,164 @@ ZigbeeOccupancySensor::ZigbeeOccupancySensor(uint8_t endpoint) : ZigbeeEP(endpoi
_ep_config = {.endpoint = _endpoint, .app_profile_id = ESP_ZB_AF_HA_PROFILE_ID, .app_device_id = ESP_ZB_HA_SIMPLE_SENSOR_DEVICE_ID, .app_device_version = 0};
}

bool ZigbeeOccupancySensor::setSensorType(uint8_t sensor_type) {
uint8_t sensor_type_bitmap = 1 << sensor_type;
bool ZigbeeOccupancySensor::setSensorType(ZigbeeOccupancySensorType sensor_type, ZigbeeOccupancySensorTypeBitmap sensor_type_bitmap) {
uint8_t sensor_type_bitmap_value = sensor_type_bitmap;
if(sensor_type_bitmap == ZIGBEE_OCCUPANCY_SENSOR_BITMAP_DEFAULT) {
sensor_type_bitmap_value = (1 << sensor_type); // Default to single sensor type if bitmap is default
}
esp_zb_attribute_list_t *occupancy_sens_cluster =
esp_zb_cluster_list_get_cluster(_cluster_list, ESP_ZB_ZCL_CLUSTER_ID_OCCUPANCY_SENSING, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);
esp_err_t ret = esp_zb_cluster_update_attr(occupancy_sens_cluster, ESP_ZB_ZCL_ATTR_OCCUPANCY_SENSING_OCCUPANCY_SENSOR_TYPE_ID, (void *)&sensor_type);
if (ret != ESP_OK) {
log_e("Failed to set sensor type: 0x%x: %s", ret, esp_err_to_name(ret));
return false;
}
ret = esp_zb_cluster_update_attr(occupancy_sens_cluster, ESP_ZB_ZCL_ATTR_OCCUPANCY_SENSING_OCCUPANCY_SENSOR_TYPE_BITMAP_ID, (void *)&sensor_type_bitmap);
ret = esp_zb_cluster_update_attr(occupancy_sens_cluster, ESP_ZB_ZCL_ATTR_OCCUPANCY_SENSING_OCCUPANCY_SENSOR_TYPE_BITMAP_ID, (void *)&sensor_type_bitmap_value);
if (ret != ESP_OK) {
log_e("Failed to set sensor type bitmap: 0x%x: %s", ret, esp_err_to_name(ret));
return false;
}

// Handle PIR attributes if PIR bit is set (bit 0)
if (sensor_type_bitmap & ZIGBEE_OCCUPANCY_SENSOR_BITMAP_PIR) {
ret = esp_zb_occupancy_sensing_cluster_add_attr(occupancy_sens_cluster, ESP_ZB_ZCL_ATTR_OCCUPANCY_SENSING_PIR_OCC_TO_UNOCC_DELAY_ID, ESP_ZB_ZCL_OCCUPANCY_SENSING_PIR_OCC_TO_UNOCC_DELAY_DEFAULT_VALUE);
if (ret != ESP_OK) {
log_e("Failed to set PIR occupied to unoccupied delay: 0x%x: %s", ret, esp_err_to_name(ret));
return false;
}
ret = esp_zb_occupancy_sensing_cluster_add_attr(occupancy_sens_cluster, ESP_ZB_ZCL_ATTR_OCCUPANCY_SENSING_PIR_UNOCC_TO_OCC_DELAY_ID, ESP_ZB_ZCL_OCCUPANCY_SENSING_PIR_UNOCC_TO_OCC_DELAY_DEFAULT_VALUE);
if (ret != ESP_OK) {
log_e("Failed to set PIR unoccupied to occupied delay: 0x%x: %s", ret, esp_err_to_name(ret));
return false;
}
uint8_t pir_threshold = ESP_ZB_ZCL_OCCUPANCY_SENSING_PIR_UNOCC_TO_OCC_THRESHOLD_DEFAULT_VALUE;
ret = esp_zb_occupancy_sensing_cluster_add_attr(occupancy_sens_cluster, ESP_ZB_ZCL_ATTR_OCCUPANCY_SENSING_PIR_UNOCC_TO_OCC_THRESHOLD_ID, &pir_threshold);
if (ret != ESP_OK) {
log_e("Failed to set PIR unoccupied to occupied threshold: 0x%x: %s", ret, esp_err_to_name(ret));
return false;
}
}

// Handle Ultrasonic attributes if Ultrasonic bit is set (bit 1)
if (sensor_type_bitmap & ZIGBEE_OCCUPANCY_SENSOR_BITMAP_ULTRASONIC) {
ret = esp_zb_occupancy_sensing_cluster_add_attr(occupancy_sens_cluster, ESP_ZB_ZCL_ATTR_OCCUPANCY_SENSING_ULTRASONIC_OCCUPIED_TO_UNOCCUPIED_DELAY_ID, ESP_ZB_ZCL_OCCUPANCY_SENSING_ULTRASONIC_OCCUPIED_TO_UNOCCUPIED_DELAY_DEFAULT_VALUE);
if (ret != ESP_OK) {
log_e("Failed to set ultrasonic occupied to unoccupied delay: 0x%x: %s", ret, esp_err_to_name(ret));
return false;
}
ret = esp_zb_occupancy_sensing_cluster_add_attr(occupancy_sens_cluster, ESP_ZB_ZCL_ATTR_OCCUPANCY_SENSING_ULTRASONIC_UNOCCUPIED_TO_OCCUPIED_DELAY_ID, ESP_ZB_ZCL_OCCUPANCY_SENSING_ULTRASONIC_UNOCCUPIED_TO_OCCUPIED_DELAY_DEFAULT_VALUE);
if (ret != ESP_OK) {
log_e("Failed to set ultrasonic unoccupied to occupied delay: 0x%x: %s", ret, esp_err_to_name(ret));
return false;
}
uint8_t ultrasonic_threshold = ESP_ZB_ZCL_OCCUPANCY_SENSING_ULTRASONIC_UNOCCUPIED_TO_OCCUPIED_THRESHOLD_DEFAULT_VALUE;
ret = esp_zb_occupancy_sensing_cluster_add_attr(occupancy_sens_cluster, ESP_ZB_ZCL_ATTR_OCCUPANCY_SENSING_ULTRASONIC_UNOCCUPIED_TO_OCCUPIED_THRESHOLD_ID, &ultrasonic_threshold);
if (ret != ESP_OK) {
log_e("Failed to set ultrasonic unoccupied to occupied threshold: 0x%x: %s", ret, esp_err_to_name(ret));
return false;
}
}

// Handle Physical Contact attributes if Physical Contact bit is set (bit 2)
if (sensor_type_bitmap & ZIGBEE_OCCUPANCY_SENSOR_BITMAP_PHYSICAL_CONTACT_AND_PIR) {
ret = esp_zb_occupancy_sensing_cluster_add_attr(occupancy_sens_cluster, ESP_ZB_ZCL_ATTR_OCCUPANCY_SENSING_PHYSICAL_CONTACT_OCCUPIED_TO_UNOCCUPIED_DELAY_ID, ESP_ZB_ZCL_OCCUPANCY_SENSING_PHYSICAL_CONTACT_OCCUPIED_TO_UNOCCUPIED_DELAY_DEFAULT_VALUE);
if (ret != ESP_OK) {
log_e("Failed to set physical contact occupied to unoccupied delay: 0x%x: %s", ret, esp_err_to_name(ret));
return false;
}
ret = esp_zb_occupancy_sensing_cluster_add_attr(occupancy_sens_cluster, ESP_ZB_ZCL_ATTR_OCCUPANCY_SENSING_PHYSICAL_CONTACT_UNOCCUPIED_TO_OCCUPIED_DELAY_ID, ESP_ZB_ZCL_OCCUPANCY_SENSING_PHYSICAL_CONTACT_UNOCCUPIED_TO_OCCUPIED_DELAY_DEFAULT_VALUE);
if (ret != ESP_OK) {
log_e("Failed to set physical contact unoccupied to occupied delay: 0x%x: %s", ret, esp_err_to_name(ret));
return false;
}
uint8_t physical_contact_threshold = ESP_ZB_ZCL_OCCUPANCY_SENSING_PHYSICAL_CONTACT_UNOCCUPIED_TO_OCCUPIED_THRESHOLD_MIN_VALUE;
ret = esp_zb_occupancy_sensing_cluster_add_attr(occupancy_sens_cluster, ESP_ZB_ZCL_ATTR_OCCUPANCY_SENSING_PHYSICAL_CONTACT_UNOCCUPIED_TO_OCCUPIED_THRESHOLD_ID, &physical_contact_threshold);
if (ret != ESP_OK) {
log_e("Failed to set physical contact unoccupied to occupied threshold: 0x%x: %s", ret, esp_err_to_name(ret));
return false;
}
}
return true;
}

bool ZigbeeOccupancySensor::setOccupiedToUnoccupiedDelay(ZigbeeOccupancySensorType sensor_type, uint16_t delay) {
esp_zb_attribute_list_t *occupancy_sens_cluster =
esp_zb_cluster_list_get_cluster(_cluster_list, ESP_ZB_ZCL_CLUSTER_ID_OCCUPANCY_SENSING, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);

esp_err_t ret;
switch (sensor_type) {
case ZIGBEE_OCCUPANCY_SENSOR_TYPE_PIR:
ret = esp_zb_cluster_update_attr(occupancy_sens_cluster, ESP_ZB_ZCL_ATTR_OCCUPANCY_SENSING_PIR_OCC_TO_UNOCC_DELAY_ID, (void *)&delay);
break;
case ZIGBEE_OCCUPANCY_SENSOR_TYPE_ULTRASONIC:
ret = esp_zb_cluster_update_attr(occupancy_sens_cluster, ESP_ZB_ZCL_ATTR_OCCUPANCY_SENSING_ULTRASONIC_OCCUPIED_TO_UNOCCUPIED_DELAY_ID, (void *)&delay);
break;
case ZIGBEE_OCCUPANCY_SENSOR_TYPE_PHYSICAL_CONTACT:
ret = esp_zb_cluster_update_attr(occupancy_sens_cluster, ESP_ZB_ZCL_ATTR_OCCUPANCY_SENSING_PHYSICAL_CONTACT_OCCUPIED_TO_UNOCCUPIED_DELAY_ID, (void *)&delay);
break;
default:
log_e("Invalid sensor type for delay setting: 0x%x", sensor_type);
return false;
}

if (ret != ESP_OK) {
log_e("Failed to set occupied to unoccupied delay: 0x%x: %s", ret, esp_err_to_name(ret));
return false;
}
return true;
}

bool ZigbeeOccupancySensor::setUnoccupiedToOccupiedDelay(ZigbeeOccupancySensorType sensor_type, uint16_t delay) {
esp_zb_attribute_list_t *occupancy_sens_cluster =
esp_zb_cluster_list_get_cluster(_cluster_list, ESP_ZB_ZCL_CLUSTER_ID_OCCUPANCY_SENSING, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);

esp_err_t ret;
switch (sensor_type) {
case ZIGBEE_OCCUPANCY_SENSOR_TYPE_PIR:
ret = esp_zb_cluster_update_attr(occupancy_sens_cluster, ESP_ZB_ZCL_ATTR_OCCUPANCY_SENSING_PIR_UNOCC_TO_OCC_DELAY_ID, (void *)&delay);
break;
case ZIGBEE_OCCUPANCY_SENSOR_TYPE_ULTRASONIC:
ret = esp_zb_cluster_update_attr(occupancy_sens_cluster, ESP_ZB_ZCL_ATTR_OCCUPANCY_SENSING_ULTRASONIC_UNOCCUPIED_TO_OCCUPIED_DELAY_ID, (void *)&delay);
break;
case ZIGBEE_OCCUPANCY_SENSOR_TYPE_PHYSICAL_CONTACT:
ret = esp_zb_cluster_update_attr(occupancy_sens_cluster, ESP_ZB_ZCL_ATTR_OCCUPANCY_SENSING_PHYSICAL_CONTACT_UNOCCUPIED_TO_OCCUPIED_DELAY_ID, (void *)&delay);
break;
default:
log_e("Invalid sensor type for delay setting: 0x%x", sensor_type);
return false;
}

if (ret != ESP_OK) {
log_e("Failed to set unoccupied to occupied delay: 0x%x: %s", ret, esp_err_to_name(ret));
return false;
}
return true;
}

bool ZigbeeOccupancySensor::setUnoccupiedToOccupiedThreshold(ZigbeeOccupancySensorType sensor_type, uint8_t threshold) {
esp_zb_attribute_list_t *occupancy_sens_cluster =
esp_zb_cluster_list_get_cluster(_cluster_list, ESP_ZB_ZCL_CLUSTER_ID_OCCUPANCY_SENSING, ESP_ZB_ZCL_CLUSTER_SERVER_ROLE);

esp_err_t ret;
switch (sensor_type) {
case ZIGBEE_OCCUPANCY_SENSOR_TYPE_PIR:
ret = esp_zb_cluster_update_attr(occupancy_sens_cluster, ESP_ZB_ZCL_ATTR_OCCUPANCY_SENSING_PIR_UNOCC_TO_OCC_THRESHOLD_ID, (void *)&threshold);
break;
case ZIGBEE_OCCUPANCY_SENSOR_TYPE_ULTRASONIC:
ret = esp_zb_cluster_update_attr(occupancy_sens_cluster, ESP_ZB_ZCL_ATTR_OCCUPANCY_SENSING_ULTRASONIC_UNOCCUPIED_TO_OCCUPIED_THRESHOLD_ID, (void *)&threshold);
break;
case ZIGBEE_OCCUPANCY_SENSOR_TYPE_PHYSICAL_CONTACT:
ret = esp_zb_cluster_update_attr(occupancy_sens_cluster, ESP_ZB_ZCL_ATTR_OCCUPANCY_SENSING_PHYSICAL_CONTACT_UNOCCUPIED_TO_OCCUPIED_THRESHOLD_ID, (void *)&threshold);
break;
default:
log_e("Invalid sensor type for threshold setting: 0x%x", sensor_type);
return false;
}

if (ret != ESP_OK) {
log_e("Failed to set unoccupied to occupied threshold: 0x%x: %s", ret, esp_err_to_name(ret));
return false;
}
return true;
}

Expand Down Expand Up @@ -77,4 +221,68 @@ bool ZigbeeOccupancySensor::report() {
return true;
}

//set attribute method -> method overridden in child class
void ZigbeeOccupancySensor::zbAttributeSet(const esp_zb_zcl_set_attr_value_message_t *message) {
if (message->info.cluster == ESP_ZB_ZCL_CLUSTER_ID_OCCUPANCY_SENSING) {
//PIR
if (message->attribute.id == ESP_ZB_ZCL_ATTR_OCCUPANCY_SENSING_PIR_OCC_TO_UNOCC_DELAY_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_U16) {
_pir_occ_to_unocc_delay = *(uint16_t *)message->attribute.data.value;
occupancyConfigChanged(ZIGBEE_OCCUPANCY_SENSOR_TYPE_PIR);
} else if (message->attribute.id == ESP_ZB_ZCL_ATTR_OCCUPANCY_SENSING_PIR_UNOCC_TO_OCC_DELAY_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_U16) {
_pir_unocc_to_occ_delay = *(uint16_t *)message->attribute.data.value;
occupancyConfigChanged(ZIGBEE_OCCUPANCY_SENSOR_TYPE_PIR);
} else if (message->attribute.id == ESP_ZB_ZCL_ATTR_OCCUPANCY_SENSING_PIR_UNOCC_TO_OCC_THRESHOLD_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_U8) {
_pir_unocc_to_occ_threshold = *(uint8_t *)message->attribute.data.value;
occupancyConfigChanged(ZIGBEE_OCCUPANCY_SENSOR_TYPE_PIR);
}
//Ultrasonic
else if (message->attribute.id == ESP_ZB_ZCL_ATTR_OCCUPANCY_SENSING_ULTRASONIC_OCCUPIED_TO_UNOCCUPIED_DELAY_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_U16) {
_ultrasonic_occ_to_unocc_delay = *(uint16_t *)message->attribute.data.value;
occupancyConfigChanged(ZIGBEE_OCCUPANCY_SENSOR_TYPE_ULTRASONIC);
} else if (message->attribute.id == ESP_ZB_ZCL_ATTR_OCCUPANCY_SENSING_ULTRASONIC_UNOCCUPIED_TO_OCCUPIED_DELAY_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_U16) {
_ultrasonic_unocc_to_occ_delay = *(uint16_t *)message->attribute.data.value;
occupancyConfigChanged(ZIGBEE_OCCUPANCY_SENSOR_TYPE_ULTRASONIC);
} else if (message->attribute.id == ESP_ZB_ZCL_ATTR_OCCUPANCY_SENSING_ULTRASONIC_UNOCCUPIED_TO_OCCUPIED_THRESHOLD_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_U8) {
_ultrasonic_unocc_to_occ_threshold = *(uint8_t *)message->attribute.data.value;
occupancyConfigChanged(ZIGBEE_OCCUPANCY_SENSOR_TYPE_ULTRASONIC);
}
//Physical Contact
else if (message->attribute.id == ESP_ZB_ZCL_ATTR_OCCUPANCY_SENSING_PHYSICAL_CONTACT_OCCUPIED_TO_UNOCCUPIED_DELAY_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_U16) {
_physical_contact_occ_to_unocc_delay = *(uint16_t *)message->attribute.data.value;
occupancyConfigChanged(ZIGBEE_OCCUPANCY_SENSOR_TYPE_PHYSICAL_CONTACT);
} else if (message->attribute.id == ESP_ZB_ZCL_ATTR_OCCUPANCY_SENSING_PHYSICAL_CONTACT_UNOCCUPIED_TO_OCCUPIED_DELAY_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_U16) {
_physical_contact_unocc_to_occ_delay = *(uint16_t *)message->attribute.data.value;
occupancyConfigChanged(ZIGBEE_OCCUPANCY_SENSOR_TYPE_PHYSICAL_CONTACT);
} else if (message->attribute.id == ESP_ZB_ZCL_ATTR_OCCUPANCY_SENSING_PHYSICAL_CONTACT_UNOCCUPIED_TO_OCCUPIED_THRESHOLD_ID && message->attribute.data.type == ESP_ZB_ZCL_ATTR_TYPE_U8) {
_physical_contact_unocc_to_occ_threshold = *(uint8_t *)message->attribute.data.value;
occupancyConfigChanged(ZIGBEE_OCCUPANCY_SENSOR_TYPE_PHYSICAL_CONTACT);
} else {
log_w("Received message ignored. Attribute ID: %d not supported for Occupancy Sensor endpoint", message->attribute.id);
}
} else {
log_w("Received message ignored. Cluster ID: %d not supported for Occupancy Sensor endpoint", message->info.cluster);
}
}

void ZigbeeOccupancySensor::occupancyConfigChanged(ZigbeeOccupancySensorType sensor_type) {
if (_on_occupancy_config_change) {
switch (sensor_type) {
case ZIGBEE_OCCUPANCY_SENSOR_TYPE_PIR:
_on_occupancy_config_change(sensor_type, _pir_occ_to_unocc_delay, _pir_unocc_to_occ_delay, _pir_unocc_to_occ_threshold);
break;
case ZIGBEE_OCCUPANCY_SENSOR_TYPE_ULTRASONIC:
_on_occupancy_config_change(sensor_type, _ultrasonic_occ_to_unocc_delay, _ultrasonic_unocc_to_occ_delay, _ultrasonic_unocc_to_occ_threshold);
break;
case ZIGBEE_OCCUPANCY_SENSOR_TYPE_PHYSICAL_CONTACT:
_on_occupancy_config_change(sensor_type, _physical_contact_occ_to_unocc_delay, _physical_contact_unocc_to_occ_delay, _physical_contact_unocc_to_occ_threshold);
break;
default:
log_e("Invalid sensor type for occupancy config change: 0x%x", sensor_type);
break;
}
} else {
log_w("No callback function set for occupancy config change");
}
}

#endif // CONFIG_ZB_ENABLED
58 changes: 56 additions & 2 deletions libraries/Zigbee/src/ep/ZigbeeOccupancySensor.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,24 @@
}
// clang-format on

enum ZigbeeOccupancySensorType{
ZIGBEE_OCCUPANCY_SENSOR_TYPE_PIR = 0,
ZIGBEE_OCCUPANCY_SENSOR_TYPE_ULTRASONIC = 1,
ZIGBEE_OCCUPANCY_SENSOR_TYPE_PIR_AND_ULTRASONIC = 2,
ZIGBEE_OCCUPANCY_SENSOR_TYPE_PHYSICAL_CONTACT = 3
};

enum ZigbeeOccupancySensorTypeBitmap{
ZIGBEE_OCCUPANCY_SENSOR_BITMAP_PIR = 0x01,
ZIGBEE_OCCUPANCY_SENSOR_BITMAP_ULTRASONIC = 0x02,
ZIGBEE_OCCUPANCY_SENSOR_BITMAP_PIR_AND_ULTRASONIC = 0x03,
// ZIGBEE_OCCUPANCY_SENSOR_BITMAP_PHYSICAL_CONTACT = 0x04, // No info in cluster specification R8
ZIGBEE_OCCUPANCY_SENSOR_BITMAP_PHYSICAL_CONTACT_AND_PIR = 0x05,
ZIGBEE_OCCUPANCY_SENSOR_BITMAP_PHYSICAL_CONTACT_AND_ULTRASONIC = 0x06,
ZIGBEE_OCCUPANCY_SENSOR_BITMAP_PHYSICAL_CONTACT_AND_PIR_AND_ULTRASONIC = 0x07,
ZIGBEE_OCCUPANCY_SENSOR_BITMAP_DEFAULT = 0xff
};

typedef struct zigbee_occupancy_sensor_cfg_s {
esp_zb_basic_cluster_cfg_t basic_cfg;
esp_zb_identify_cluster_cfg_t identify_cfg;
Expand All @@ -44,11 +62,47 @@ class ZigbeeOccupancySensor : public ZigbeeEP {
// Set the occupancy value. True for occupied, false for unoccupied
bool setOccupancy(bool occupied);

// Set the sensor type, see esp_zb_zcl_occupancy_sensing_occupancy_sensor_type_t
bool setSensorType(uint8_t sensor_type);
// Set the sensor type, see ZigbeeOccupancySensorType
bool setSensorType(ZigbeeOccupancySensorType sensor_type, ZigbeeOccupancySensorTypeBitmap sensor_type_bitmap = ZIGBEE_OCCUPANCY_SENSOR_BITMAP_DEFAULT);

// Set the occupied to unoccupied delay
// Specifies the time delay, in seconds, before the sensor changes to its unoccupied state after the last detection of movement in the sensed area.
bool setOccupiedToUnoccupiedDelay(ZigbeeOccupancySensorType sensor_type, uint16_t delay);

// Set the unoccupied to occupied delay
// Specifies the time delay, in seconds, before the sensor changes to its occupied state after the detection of movement in the sensed area.
bool setUnoccupiedToOccupiedDelay(ZigbeeOccupancySensorType sensor_type, uint16_t delay);

// Set the unoccupied to occupied threshold
// Specifies the number of movement detection events that must occur in the period unoccupied to occupied delay, before the sensor changes to its occupied state.
bool setUnoccupiedToOccupiedThreshold(ZigbeeOccupancySensorType sensor_type, uint8_t threshold);

void onOccupancyConfigChange(void (*callback)(ZigbeeOccupancySensorType sensor_type, uint16_t occ_to_unocc_delay, uint16_t unocc_to_occ_delay, uint8_t unocc_to_occ_threshold)) {
_on_occupancy_config_change = callback;
}
// Report the occupancy value
bool report();

private:
void zbAttributeSet(const esp_zb_zcl_set_attr_value_message_t *message) override;

void (*_on_occupancy_config_change)(ZigbeeOccupancySensorType sensor_type, uint16_t occ_to_unocc_delay, uint16_t unocc_to_occ_delay, uint8_t unocc_to_occ_threshold);
void occupancyConfigChanged(ZigbeeOccupancySensorType sensor_type);

// PIR sensor configuration
uint16_t _pir_occ_to_unocc_delay;
uint16_t _pir_unocc_to_occ_delay;
uint8_t _pir_unocc_to_occ_threshold;

// Ultrasonic sensor configuration
uint16_t _ultrasonic_occ_to_unocc_delay;
uint16_t _ultrasonic_unocc_to_occ_delay;
uint8_t _ultrasonic_unocc_to_occ_threshold;

// Physical contact sensor configuration
uint16_t _physical_contact_occ_to_unocc_delay;
uint16_t _physical_contact_unocc_to_occ_delay;
uint8_t _physical_contact_unocc_to_occ_threshold;
};

#endif // CONFIG_ZB_ENABLED
Loading