diff --git a/Makefile b/Makefile index 30886706c5..327e062201 100644 --- a/Makefile +++ b/Makefile @@ -116,7 +116,8 @@ INCLUDE_PATHS := -I$(SRC_PATH) -I$(SRC_PATH)/includes -I$(SRC_PATH)/common -I$( -I$(SDK_PATH)/zigbee/zbapi \ -I$(SDK_PATH)/zigbee/zbhci \ -I$(SDK_PATH)/zigbee/zcl \ --I$(SDK_PATH)/zigbee/zdo +-I$(SDK_PATH)/zigbee/zdo \ +-I$(SDK_PATH)/zigbee/aps GCC_FLAGS += $(TEL_CHIP) $(DEVICE_DEFS) diff --git a/src/app_cfg.h b/src/app_cfg.h index 565a2161dc..dedb7e50f9 100644 --- a/src/app_cfg.h +++ b/src/app_cfg.h @@ -91,6 +91,7 @@ extern "C" { #define TOUCHLINK_SUPPORT 0 #define FIND_AND_BIND_SUPPORT 0 #define REJOIN_FAILURE_TIMER 1 +#define ZCL_SCENE_SUPPORT 1 #define GP_SUPPORT_ENABLE 1 diff --git a/src/base_components/button.c b/src/base_components/button.c index bc13a84f0b..ddaa57876b 100644 --- a/src/base_components/button.c +++ b/src/base_components/button.c @@ -16,6 +16,7 @@ void btn_init(button_t *button) button->pressed = true; button->long_pressed = true; button->long_released = true; + button->timed_out = true; } } @@ -50,10 +51,8 @@ void btn_update_debounced(button_t *button, u8 is_pressed) printf("Press detected\r\n"); button->pressed_at_ms = now; button->long_released = false; - if (button->on_press != NULL) - { - button->on_press(button->callback_param); - } + button->timed_out = false; + if (now - button->released_at_ms < button->multi_press_duration_ms) { button->multi_press_cnt += 1; @@ -66,6 +65,12 @@ void btn_update_debounced(button_t *button, u8 is_pressed) else { button->multi_press_cnt = 1; + button->multi_release_cnt = 0; + } + + if (button->on_press != NULL) + { + button->on_press(button->callback_param); } } else if (button->pressed && !is_pressed) @@ -73,16 +78,23 @@ void btn_update_debounced(button_t *button, u8 is_pressed) printf("Release detected\r\n"); button->released_at_ms = now; button->long_pressed = false; - if (button->on_release != NULL) - { - button->on_release(button->callback_param); - } + button->timed_out = false; if (now - button->pressed_at_ms > button->multi_press_duration_ms) { button->multi_press_cnt = 0; + button->multi_release_cnt = 1; + } else { + button->multi_release_cnt++; + } + + if (button->on_release != NULL) + { + button->on_release(button->callback_param); } } + button->pressed = is_pressed; + if (is_pressed && !button->long_pressed && (button->long_press_duration_ms > 0) && (button->long_press_duration_ms < (now - button->pressed_at_ms))) { button->long_pressed = true; @@ -102,5 +114,18 @@ void btn_update_debounced(button_t *button, u8 is_pressed) button->on_long_release(button->callback_param); } } - ; + + if (!button->timed_out) + { + if (is_pressed && button->pressed_at_ms + button->timeout_duration_ms < now && button->on_timeout_pressed) + { + button->timed_out = true; + button->on_timeout_pressed(button->callback_param); + } + else if (!is_pressed && button->released_at_ms + button->timeout_duration_ms < now && button->on_timeout_released) + { + button->timed_out = true; + button->on_timeout_released(button->callback_param); + } + } } diff --git a/src/base_components/button.h b/src/base_components/button.h index 724f377d4f..bc579decca 100644 --- a/src/base_components/button.h +++ b/src/base_components/button.h @@ -15,17 +15,22 @@ typedef struct u8 pressed; u8 long_pressed; u8 long_released; + u8 timed_out; u32 pressed_at_ms; u32 released_at_ms; u32 long_press_duration_ms; u32 multi_press_duration_ms; + u32 timeout_duration_ms; u8 multi_press_cnt; + u8 multi_release_cnt; u8 debounce_last_state; u32 debounce_last_change; - ev_button_callback_t on_press; - ev_button_callback_t on_long_press; - ev_button_callback_t on_release; - ev_button_callback_t on_long_release; + ev_button_callback_t on_press; + ev_button_callback_t on_long_press; + ev_button_callback_t on_release; + ev_button_callback_t on_long_release; + ev_button_callback_t on_timeout_pressed; + ev_button_callback_t on_timeout_released; ev_button_multi_press_callback_t on_multi_press; void * callback_param; }button_t; diff --git a/src/custom_zcl/zcl_onoff_configuration.h b/src/custom_zcl/zcl_onoff_configuration.h index d966970651..d20f350938 100644 --- a/src/custom_zcl/zcl_onoff_configuration.h +++ b/src/custom_zcl/zcl_onoff_configuration.h @@ -19,6 +19,11 @@ #define ZCL_ATTRID_ONOFF_CONFIGURATION_SWITCH_LONG_PRESS_DUR 0xff03 #define ZCL_ATTRID_ONOFF_CONFIGURATION_SWITCH_LEVEL_MOVE_RATE 0xff04 #define ZCL_ATTRID_ONOFF_CONFIGURATION_SWITCH_BINDING_MODE 0xff05 +#define ZCL_ATTRID_ONOFF_CONFIGURATION_SCENE_NEXT_IDX 0xff06 +#define ZCL_ATTRID_ONOFF_CONFIGURATION_SCENE_COUNT 0xff07 +#define ZCL_ATTRID_ONOFF_CONFIGURATION_SCENE_OFFSET 0xff08 +#define ZCL_ATTRID_ONOFF_CONFIGURATION_SCENE_GROUP_ID 0xff09 +#define ZCL_ATTRID_ONOFF_CONFIGURATION_SCENE_RECALL_TIME 0xff0a #define ZCL_ONOFF_CONFIGURATION_SWITCH_TYPE_TOGGLE 0x00 diff --git a/src/zigbee/binding_table.h b/src/zigbee/binding_table.h new file mode 100644 index 0000000000..421c0ad36e --- /dev/null +++ b/src/zigbee/binding_table.h @@ -0,0 +1,10 @@ +#ifndef _BINDING_TABLE_H_ +#define _BINDING_TABLE_H_ + +#include "aps_api.h" + +#define BINDING_TABLE_FOR_EACH(cluster, endpoint, out_ptr) \ + for (int i__ = 0; i__ < APS_BINDING_TABLE_NUM; ++i__) \ + if (g_apsBindingTbl[i__].used && g_apsBindingTbl[i__].clusterId == (cluster) && g_apsBindingTbl[i__].srcEp == (endpoint) && ((out_ptr) = (&g_apsBindingTbl[i__]))) + +#endif \ No newline at end of file diff --git a/src/zigbee/general_commands.c b/src/zigbee/general_commands.c index 10506699de..79740e2ca8 100644 --- a/src/zigbee/general_commands.c +++ b/src/zigbee/general_commands.c @@ -26,7 +26,7 @@ void device_zclWriteReqCmd(u8 endpoint, u16 clusterId, zclWriteCmd_t *pWriteReqC { if (clusterId == ZCL_CLUSTER_GEN_ON_OFF_SWITCH_CONFIG) { - switch_cluster_callback_attr_write_trampoline(endpoint); + switch_cluster_callback_attr_write_trampoline(endpoint, clusterId, pWriteReqCmd); } if (clusterId == ZCL_CLUSTER_GEN_ON_OFF) { diff --git a/src/zigbee/switch_cluster.c b/src/zigbee/switch_cluster.c index 099a3fb747..a66a1af6d3 100644 --- a/src/zigbee/switch_cluster.c +++ b/src/zigbee/switch_cluster.c @@ -10,6 +10,7 @@ #include "zcl_include.h" #include "base_components/relay.h" #include "configs/nv_slots_cfg.h" +#include "binding_table.h" #define MULTI_PRESS_CNT_TO_RESET 10 @@ -19,10 +20,12 @@ const u16 multistate_num_of_states = 3; #define MULTISTATE_NOT_PRESSED 0 -#define MULTISTATE_PRESS 1 +#define MULTISTATE_SINGLE_PRESS 1 #define MULTISTATE_LONG_PRESS 2 #define MULTISTATE_POSITION_ON 3 #define MULTISTATE_POSITION_OFF 4 +#define MULTISTATE_DOUBLE_PRESS 5 +#define MULTISTATE_SINGLE_RELEASE 6 extern zigbee_relay_cluster relay_clusters[]; @@ -33,6 +36,8 @@ void switch_cluster_on_button_release(zigbee_switch_cluster *cluster); void switch_cluster_on_button_long_press(zigbee_switch_cluster *cluster); void switch_cluster_on_button_long_release(zigbee_switch_cluster *cluster); void switch_cluster_on_button_multi_press(zigbee_switch_cluster *cluster, u8 press_count); +void switch_cluster_on_button_timeout_pressed(zigbee_switch_cluster *cluster); +void switch_cluster_on_button_timeout_released(zigbee_switch_cluster *cluster); zigbee_switch_cluster *switch_cluster_by_endpoint[10]; @@ -42,17 +47,53 @@ void switch_cluster_on_write_attr(zigbee_switch_cluster *cluster); void switch_cluster_report_action(zigbee_switch_cluster *cluster); +static void switch_cluster_fixup_scene_attrs(zigbee_switch_cluster *cluster); +static void switch_cluster_report_next_scene(zigbee_switch_cluster *cluster); + +bool switch_cluster_scenes_is_enabled(zigbee_switch_cluster *cluster); + +static void switch_cluster_binding_recall_scene(zigbee_switch_cluster *cluster); status_t switch_cluster_callback_trampoline(zclIncomingAddrInfo_t *pAddrInfo, u8 cmdId, void *cmdPayload) { return(ZCL_STA_SUCCESS); } -void switch_cluster_callback_attr_write_trampoline(u8 clusterId) +void switch_cluster_callback_attr_write_trampoline(u8 endpoint, u16 clusterId, zclWriteCmd_t *pWriteReqCmd) { - switch_cluster_on_write_attr(switch_cluster_by_endpoint[clusterId]); + (void)pWriteReqCmd; + zigbee_switch_cluster *cluster = switch_cluster_by_endpoint[endpoint]; + + switch_cluster_on_write_attr(cluster); + + if (clusterId == 8 || clusterId == 9 || clusterId == 10) + { + switch_cluster_fixup_scene_attrs(cluster); + } + + if (clusterId == 3) // relay mode + { + cluster->multistate_state = MULTISTATE_NOT_PRESSED; + switch_cluster_report_action(cluster); + } } +// (mandatory) Cluster revisions +static u16 onoff_cluster_revision = 0x0002; +static const zclAttrInfo_t onOff_client_attrs[] = { + {.id = 0, .type = ZCL_DATA_TYPE_UINT16, .access = ACCESS_CONTROL_READ, .data = (u8*)&onoff_cluster_revision}, +}; + +static u16 scenes_cluster_revision = 0x0003; +static const zclAttrInfo_t scenes_client_attrs[] = { + {.id = 0, .type = ZCL_DATA_TYPE_UINT16, .access = ACCESS_CONTROL_READ, .data = (u8*)&scenes_cluster_revision}, +}; + +// Level has the same revision as Scene cluster +#define level_client_attrs scenes_client_attrs + +static u16 multistate_input_cluster_revision = 0x0001; + void switch_cluster_add_to_endpoint(zigbee_switch_cluster *cluster, zigbee_endpoint *endpoint) { switch_cluster_by_endpoint[endpoint->index] = cluster; @@ -63,8 +104,10 @@ void switch_cluster_add_to_endpoint(zigbee_switch_cluster *cluster, zigbee_endpo cluster->button->on_release = (ev_button_callback_t)switch_cluster_on_button_release; cluster->button->on_long_press = (ev_button_callback_t)switch_cluster_on_button_long_press; cluster->button->on_long_release = (ev_button_callback_t)switch_cluster_on_button_long_release; - cluster->button->on_multi_press = (ev_button_multi_press_callback_t)switch_cluster_on_button_multi_press; + cluster->button->on_timeout_pressed = (ev_button_callback_t)switch_cluster_on_button_timeout_pressed; + cluster->button->on_timeout_released = (ev_button_callback_t)switch_cluster_on_button_timeout_released; cluster->button->callback_param = cluster; + cluster->button->timeout_duration_ms = 250; SETUP_ATTR(0, ZCL_ATTRID_ONOFF_CONFIGURATION_SWITCH_TYPE, ZCL_DATA_TYPE_ENUM8, ACCESS_CONTROL_READ, cluster->mode); SETUP_ATTR(1, ZCL_ATTRID_ONOFF_CONFIGURATION_SWITCH_ACTIONS, ZCL_DATA_TYPE_ENUM8, ACCESS_CONTROL_READ | ACCESS_CONTROL_WRITE, cluster->action); @@ -74,13 +117,21 @@ void switch_cluster_add_to_endpoint(zigbee_switch_cluster *cluster, zigbee_endpo SETUP_ATTR(5, ZCL_ATTRID_ONOFF_CONFIGURATION_SWITCH_LONG_PRESS_DUR, ZCL_DATA_TYPE_UINT16, ACCESS_CONTROL_READ | ACCESS_CONTROL_WRITE, cluster->button->long_press_duration_ms); SETUP_ATTR(6, ZCL_ATTRID_ONOFF_CONFIGURATION_SWITCH_LEVEL_MOVE_RATE, ZCL_DATA_TYPE_UINT8, ACCESS_CONTROL_READ | ACCESS_CONTROL_WRITE, cluster->level_move->rate); SETUP_ATTR(7, ZCL_ATTRID_ONOFF_CONFIGURATION_SWITCH_BINDING_MODE, ZCL_DATA_TYPE_ENUM8, ACCESS_CONTROL_READ | ACCESS_CONTROL_WRITE, cluster->binded_mode); + SETUP_ATTR(8, ZCL_ATTRID_ONOFF_CONFIGURATION_SCENE_NEXT_IDX, ZCL_DATA_TYPE_UINT8, ACCESS_CONTROL_READ | ACCESS_CONTROL_AUTH_WRITE | ACCESS_CONTROL_REPORTABLE, cluster->out_scene_next); + SETUP_ATTR(9, ZCL_ATTRID_ONOFF_CONFIGURATION_SCENE_COUNT, ZCL_DATA_TYPE_UINT8, ACCESS_CONTROL_READ | ACCESS_CONTROL_WRITE, cluster->out_scene_count); + SETUP_ATTR(10, ZCL_ATTRID_ONOFF_CONFIGURATION_SCENE_OFFSET, ZCL_DATA_TYPE_UINT8, ACCESS_CONTROL_READ | ACCESS_CONTROL_WRITE, cluster->out_scene_start_offset); + SETUP_ATTR(11, ZCL_ATTRID_ONOFF_CONFIGURATION_SCENE_GROUP_ID, ZCL_DATA_TYPE_UINT16, ACCESS_CONTROL_READ | ACCESS_CONTROL_WRITE, cluster->out_scene_group_id); + SETUP_ATTR(12, ZCL_ATTRID_ONOFF_CONFIGURATION_SCENE_RECALL_TIME, ZCL_DATA_TYPE_UINT16, ACCESS_CONTROL_READ | ACCESS_CONTROL_WRITE, cluster->scene_recall_time); + // This is mandatory on everything + SETUP_ATTR(13, ZCL_ATTRID_GLOBAL_CLUSTER_REVISION, ZCL_DATA_TYPE_UINT16, ACCESS_CONTROL_READ, zcl_attr_global_clusterRevision); + // Configuration zigbee_endpoint_add_cluster(endpoint, 1, ZCL_CLUSTER_GEN_ON_OFF_SWITCH_CONFIG); zcl_specClusterInfo_t *info_conf = zigbee_endpoint_reserve_info(endpoint); info_conf->clusterId = ZCL_CLUSTER_GEN_ON_OFF_SWITCH_CONFIG; info_conf->manuCode = MANUFACTURER_CODE_NONE; - info_conf->attrNum = 8; + info_conf->attrNum = SWITCH_CLUSTER_ONOFF_CONFIGURATION_ATTRS; info_conf->attrTbl = cluster->attr_infos; info_conf->clusterRegisterFunc = zcl_onoff_configuration_register; info_conf->clusterAppCb = switch_cluster_callback_trampoline; @@ -90,8 +141,8 @@ void switch_cluster_add_to_endpoint(zigbee_switch_cluster *cluster, zigbee_endpo zcl_specClusterInfo_t *info = zigbee_endpoint_reserve_info(endpoint); info->clusterId = ZCL_CLUSTER_GEN_ON_OFF; info->manuCode = MANUFACTURER_CODE_NONE; - info->attrNum = 0; - info->attrTbl = NULL; + info->attrNum = 1; + info->attrTbl = onOff_client_attrs; info->clusterRegisterFunc = zcl_onOff_register; info->clusterAppCb = switch_cluster_callback_trampoline; @@ -99,13 +150,14 @@ void switch_cluster_add_to_endpoint(zigbee_switch_cluster *cluster, zigbee_endpo SETUP_ATTR_FOR_TABLE(cluster->multistate_attr_infos, 1, ZCL_ATTRID_MULTISTATE_INPUT_OUT_OF_SERVICE, ZCL_DATA_TYPE_BOOLEAN, ACCESS_CONTROL_READ, multistate_out_of_service); SETUP_ATTR_FOR_TABLE(cluster->multistate_attr_infos, 2, ZCL_ATTRID_MULTISTATE_INPUT_PRESENT_VALUE, ZCL_DATA_TYPE_UINT16, ACCESS_CONTROL_READ | ACCESS_CONTROL_REPORTABLE, cluster->multistate_state); SETUP_ATTR_FOR_TABLE(cluster->multistate_attr_infos, 3, ZCL_ATTRID_MULTISTATE_INPUT_STATUS_FLAGS, ZCL_DATA_TYPE_BITMAP8, ACCESS_CONTROL_READ, multistate_flags); + SETUP_ATTR_FOR_TABLE(cluster->multistate_attr_infos, 4, ZCL_ATTRID_GLOBAL_CLUSTER_REVISION, ZCL_DATA_TYPE_UINT16, ACCESS_CONTROL_READ, multistate_input_cluster_revision); // Output zigbee_endpoint_add_cluster(endpoint, 1, ZCL_CLUSTER_GEN_MULTISTATE_INPUT_BASIC); zcl_specClusterInfo_t *info_multistate = zigbee_endpoint_reserve_info(endpoint); info_multistate->clusterId = ZCL_CLUSTER_GEN_MULTISTATE_INPUT_BASIC; info_multistate->manuCode = MANUFACTURER_CODE_NONE; - info_multistate->attrNum = 4; + info_multistate->attrNum = 5; info_multistate->attrTbl = cluster->multistate_attr_infos; info_multistate->clusterRegisterFunc = zcl_multistate_input_register; info_multistate->clusterAppCb = NULL; @@ -115,10 +167,20 @@ void switch_cluster_add_to_endpoint(zigbee_switch_cluster *cluster, zigbee_endpo zcl_specClusterInfo_t *info_level = zigbee_endpoint_reserve_info(endpoint); info_level->clusterId = ZCL_CLUSTER_GEN_LEVEL_CONTROL; info_level->manuCode = MANUFACTURER_CODE_NONE; - info_level->attrNum = 0; - info_level->attrTbl = NULL; + info_level->attrNum = 1; + info_level->attrTbl = level_client_attrs; info_level->clusterRegisterFunc = zcl_level_register; info_level->clusterAppCb = switch_cluster_callback_trampoline; + + // Scenes output for other devices + zigbee_endpoint_add_cluster(endpoint, 0, ZCL_CLUSTER_GEN_SCENES); + zcl_specClusterInfo_t *info_scenes = zigbee_endpoint_reserve_info(endpoint); + info_scenes->clusterId = ZCL_CLUSTER_GEN_SCENES; + info_level->manuCode = MANUFACTURER_CODE_NONE; + info_level->attrNum = 1; + info_level->attrTbl = scenes_client_attrs; + info_level->clusterRegisterFunc = zcl_scene_register; + info_level->clusterAppCb = switch_cluster_callback_trampoline; } @@ -308,6 +370,13 @@ void switch_cluster_on_button_press(zigbee_switch_cluster *cluster) } if (cluster->mode == ZCL_ONOFF_CONFIGURATION_SWITCH_TYPE_MOMENTARY) { + if (cluster->multistate_state == MULTISTATE_SINGLE_RELEASE) { + switch_cluster_binding_recall_scene(cluster); + + cluster->multistate_state = MULTISTATE_DOUBLE_PRESS; + switch_cluster_report_action(cluster); + return; + } if (cluster->relay_mode == ZCL_ONOFF_CONFIGURATION_RELAY_MODE_RISE) { switch_cluster_relay_action_on(cluster); @@ -317,27 +386,40 @@ void switch_cluster_on_button_press(zigbee_switch_cluster *cluster) switch_cluster_binding_action_on(cluster); } - cluster->multistate_state = MULTISTATE_PRESS; + cluster->multistate_state = MULTISTATE_SINGLE_PRESS; switch_cluster_report_action(cluster); return; } if (cluster->mode == ZCL_ONOFF_CONFIGURATION_SWITCH_TYPE_MOMENTARY_NC) { - if (cluster->multistate_state != MULTISTATE_LONG_PRESS) { - if (cluster->relay_mode == ZCL_ONOFF_CONFIGURATION_RELAY_MODE_SHORT) { - switch_cluster_relay_action_on(cluster); + if (!switch_cluster_scenes_is_enabled(cluster)) { + if (cluster->relay_mode == ZCL_ONOFF_CONFIGURATION_RELAY_MODE_SHORT) { + switch_cluster_relay_action_on(cluster); + } + if (cluster->binded_mode == ZCL_ONOFF_CONFIGURATION_BINDED_MODE_SHORT) { + switch_cluster_binding_action_on(cluster); + } + + cluster->multistate_state = MULTISTATE_NOT_PRESSED; + switch_cluster_report_action(cluster); } - if (cluster->binded_mode == ZCL_ONOFF_CONFIGURATION_BINDED_MODE_SHORT) { - switch_cluster_binding_action_on(cluster); + else if (cluster->multistate_state == MULTISTATE_SINGLE_PRESS) + { + cluster->multistate_state = MULTISTATE_SINGLE_RELEASE; + switch_cluster_report_action(cluster); + } else { + cluster->multistate_state = MULTISTATE_NOT_PRESSED; + switch_cluster_report_action(cluster); } } else { // This is end of long press, send zcl_level stop switch_cluster_level_stop(cluster); + + cluster->multistate_state = MULTISTATE_NOT_PRESSED; + switch_cluster_report_action(cluster); } - cluster->multistate_state = MULTISTATE_NOT_PRESSED; - switch_cluster_report_action(cluster); return; } } @@ -359,25 +441,43 @@ void switch_cluster_on_button_release(zigbee_switch_cluster *cluster) } if (cluster->mode == ZCL_ONOFF_CONFIGURATION_SWITCH_TYPE_MOMENTARY) { - if (cluster->multistate_state != MULTISTATE_LONG_PRESS) { - if (cluster->relay_mode == ZCL_ONOFF_CONFIGURATION_RELAY_MODE_SHORT) { - switch_cluster_relay_action_on(cluster); + if (!switch_cluster_scenes_is_enabled(cluster)) { + if (cluster->relay_mode == ZCL_ONOFF_CONFIGURATION_RELAY_MODE_SHORT) { + switch_cluster_relay_action_on(cluster); + } + if (cluster->binded_mode == ZCL_ONOFF_CONFIGURATION_BINDED_MODE_SHORT) { + switch_cluster_binding_action_on(cluster); + } + + cluster->multistate_state = MULTISTATE_NOT_PRESSED; + switch_cluster_report_action(cluster); } - if (cluster->binded_mode == ZCL_ONOFF_CONFIGURATION_BINDED_MODE_SHORT) { - switch_cluster_binding_action_on(cluster); + else if (cluster->multistate_state == MULTISTATE_SINGLE_PRESS) + { + cluster->multistate_state = MULTISTATE_SINGLE_RELEASE; + switch_cluster_report_action(cluster); + } else { + cluster->multistate_state = MULTISTATE_NOT_PRESSED; + switch_cluster_report_action(cluster); } } else { // This is end of long press, send zcl_level stop switch_cluster_level_stop(cluster); - } - cluster->multistate_state = MULTISTATE_NOT_PRESSED; - switch_cluster_report_action(cluster); - return; + cluster->multistate_state = MULTISTATE_NOT_PRESSED; + switch_cluster_report_action(cluster); + } } if (cluster->mode == ZCL_ONOFF_CONFIGURATION_SWITCH_TYPE_MOMENTARY_NC) { + if (cluster->multistate_state == MULTISTATE_SINGLE_RELEASE) { + switch_cluster_binding_recall_scene(cluster); + + cluster->multistate_state = MULTISTATE_DOUBLE_PRESS; + switch_cluster_report_action(cluster); + return; + } if (cluster->relay_mode == ZCL_ONOFF_CONFIGURATION_RELAY_MODE_RISE) { switch_cluster_relay_action_on(cluster); @@ -387,7 +487,7 @@ void switch_cluster_on_button_release(zigbee_switch_cluster *cluster) switch_cluster_binding_action_on(cluster); } - cluster->multistate_state = MULTISTATE_PRESS; + cluster->multistate_state = MULTISTATE_SINGLE_PRESS; switch_cluster_report_action(cluster); return; } @@ -471,13 +571,67 @@ void switch_cluster_on_button_multi_press(zigbee_switch_cluster *cluster, u8 pre } } +void switch_cluster_on_button_timeout_pressed(zigbee_switch_cluster *cluster) +{ + if (cluster->mode == ZCL_ONOFF_CONFIGURATION_SWITCH_TYPE_TOGGLE) + { + // Shouldn't hook into this + } + + if (cluster->mode == ZCL_ONOFF_CONFIGURATION_SWITCH_TYPE_MOMENTARY) + { + // Hook into switch_cluster_on_button_released + } + + if (switch_cluster_scenes_is_enabled(cluster) && cluster->multistate_state == MULTISTATE_SINGLE_RELEASE) + { + if (cluster->relay_mode == ZCL_ONOFF_CONFIGURATION_RELAY_MODE_SHORT) { + switch_cluster_relay_action_on(cluster); + } + if (cluster->binded_mode == ZCL_ONOFF_CONFIGURATION_BINDED_MODE_SHORT) { + switch_cluster_binding_action_on(cluster); + } + + cluster->multistate_state = MULTISTATE_NOT_PRESSED; + switch_cluster_report_action(cluster); + } +} + +void switch_cluster_on_button_timeout_released(zigbee_switch_cluster *cluster) +{ + if (cluster->mode == ZCL_ONOFF_CONFIGURATION_SWITCH_TYPE_TOGGLE) + { + // Shouldn't hook into this + } + + if (cluster->mode == ZCL_ONOFF_CONFIGURATION_SWITCH_TYPE_MOMENTARY_NC) + { + // Hook into switch_cluster_on_button_released + } + + if (switch_cluster_scenes_is_enabled(cluster) && cluster->multistate_state == MULTISTATE_SINGLE_RELEASE) + { + if (cluster->relay_mode == ZCL_ONOFF_CONFIGURATION_RELAY_MODE_SHORT) { + switch_cluster_relay_action_on(cluster); + } + if (cluster->binded_mode == ZCL_ONOFF_CONFIGURATION_BINDED_MODE_SHORT) { + switch_cluster_binding_action_on(cluster); + } + + cluster->multistate_state = MULTISTATE_NOT_PRESSED; + switch_cluster_report_action(cluster); + } +} + void switch_cluster_on_write_attr(zigbee_switch_cluster *cluster) { switch_cluster_store_attrs_to_nv(cluster); } -zigbee_switch_cluster_config nv_config_buffer; - +zigbee_switch_cluster_config nv_config_buffer = { + .out_scene_group_id = 0xffff, + .scene_recall_time = 0xffff, +}; void switch_cluster_store_attrs_to_nv(zigbee_switch_cluster *cluster) { @@ -488,6 +642,10 @@ void switch_cluster_store_attrs_to_nv(zigbee_switch_cluster *cluster) nv_config_buffer.button_long_press_duration = cluster->button->long_press_duration_ms; nv_config_buffer.level_move_rate = cluster->level_move->rate; nv_config_buffer.binded_mode = cluster->binded_mode; + nv_config_buffer.out_scene_count = cluster->out_scene_count; + nv_config_buffer.out_scene_start_offset = cluster->out_scene_start_offset; + nv_config_buffer.out_scene_group_id = cluster->out_scene_group_id; + nv_config_buffer.scene_recall_time = cluster->scene_recall_time; nv_flashWriteNew(1, NV_MODULE_APP, NV_ITEM_SWITCH_CLUSTER_DATA(cluster->switch_idx), sizeof(zigbee_switch_cluster_config), (u8 *)&nv_config_buffer); } @@ -507,6 +665,10 @@ void switch_cluster_load_attrs_from_nv(zigbee_switch_cluster *cluster) cluster->button->long_press_duration_ms = nv_config_buffer.button_long_press_duration; cluster->level_move->rate = nv_config_buffer.level_move_rate; cluster->binded_mode = nv_config_buffer.binded_mode; + cluster->out_scene_count = nv_config_buffer.out_scene_count; + cluster->out_scene_start_offset = nv_config_buffer.out_scene_start_offset; + cluster->out_scene_group_id = nv_config_buffer.out_scene_group_id; + cluster->scene_recall_time = nv_config_buffer.scene_recall_time; } void switch_cluster_report_action(zigbee_switch_cluster *cluster) @@ -525,3 +687,115 @@ void switch_cluster_report_action(zigbee_switch_cluster *cluster) ZCL_CLUSTER_GEN_MULTISTATE_INPUT_BASIC, pAttrEntry->id, pAttrEntry->type, pAttrEntry->data); } } + +static void switch_cluster_report_next_scene(zigbee_switch_cluster *cluster) +{ + if (zb_isDeviceJoinedNwk()) + { + epInfo_t dstEpInfo; + TL_SETSTRUCTCONTENT(dstEpInfo, 0); + + dstEpInfo.profileId = HA_PROFILE_ID; + dstEpInfo.dstAddrMode = APS_DSTADDR_EP_NOTPRESETNT; + + zclAttrInfo_t *pAttrEntry = &cluster->attr_infos[8]; + zcl_sendReportCmd(cluster->endpoint, &dstEpInfo, TRUE, ZCL_FRAME_SERVER_CLIENT_DIR, + ZCL_CLUSTER_GEN_ON_OFF_SWITCH_CONFIG, pAttrEntry->id, pAttrEntry->type, pAttrEntry->data); + } +} + +static void switch_cluster_fixup_scene_attrs(zigbee_switch_cluster *cluster) +{ + u8 next_normal = cluster->out_scene_next - cluster->out_scene_start_offset; + if (next_normal < cluster->out_scene_count || next_normal == 0) + { + return; + } + + // Basically reset to 0 + cluster->out_scene_next = cluster->out_scene_start_offset; + switch_cluster_report_next_scene(cluster); +} + +static void switch_cluster_binding_recall_scene(zigbee_switch_cluster *cluster) +{ + if (!switch_cluster_scenes_is_enabled(cluster) || !zb_isDeviceJoinedNwk()) + { + return; + } + + u8 next_scene = cluster->out_scene_next++; + if (cluster->out_scene_next - cluster->out_scene_start_offset >= cluster->out_scene_count) + cluster->out_scene_next = cluster->out_scene_start_offset; + + if (cluster->out_scene_group_id != 0xffff) + { + epInfo_t dstEpInfo; + TL_SETSTRUCTCONTENT(dstEpInfo, 0); + + dstEpInfo.profileId = HA_PROFILE_ID; + dstEpInfo.dstAddrMode = APS_DSTADDR_EP_NOTPRESETNT; + + recallScene_t recallScene = { + .groupId = cluster->out_scene_group_id, + .sceneId = next_scene, + .transTime = cluster->scene_recall_time, + }; + + zcl_scene_recallScene(cluster->endpoint, &dstEpInfo, true, ZCL_SEQ_NUM, &recallScene); + } + else + { + aps_data_req_t scenesInfo; + TL_SETSTRUCTCONTENT(scenesInfo, 0); + + scenesInfo.profile_id = HA_PROFILE_ID; + scenesInfo.cluster_id = ZCL_CLUSTER_GEN_SCENES; + scenesInfo.src_endpoint = cluster->endpoint; + scenesInfo.dst_addr_mode = 0x00; + + aps_binding_entry_t *e; + + BINDING_TABLE_FOR_EACH(ZCL_CLUSTER_GEN_SCENES, cluster->endpoint, e) + { + u16 group_id = 0xffff; + + epInfo_t dstEpInfo; + TL_SETSTRUCTCONTENT(dstEpInfo, 0); + + if (e->dstAddrMode == APS_BIND_DST_ADDR_GROUP) + { + group_id = e->groupAddr; + + dstEpInfo.profileId = HA_PROFILE_ID; + dstEpInfo.dstAddrMode = APS_SHORT_GROUPADDR_NOEP; + dstEpInfo.dstAddr.shortAddr = group_id; + } + else + { + group_id = 0; + + dstEpInfo.profileId = HA_PROFILE_ID; + dstEpInfo.dstAddrMode = APS_LONG_DSTADDR_WITHEP; + dstEpInfo.dstEp = e->dstExtAddrInfo.dstEp; + + ZB_IEEE_ADDR_COPY(dstEpInfo.dstAddr.extAddr, e->dstExtAddrInfo.extAddr); + } + + recallScene_t recallScene = { + .groupId = cluster->out_scene_group_id, + .sceneId = next_scene, + .transTime = cluster->scene_recall_time, + }; + + zcl_scene_recallScene(cluster->endpoint, &dstEpInfo, true, ZCL_SEQ_NUM, &recallScene); + } + } + + switch_cluster_report_next_scene(cluster); +} + +bool switch_cluster_scenes_is_enabled(zigbee_switch_cluster *cluster) +{ + return cluster->out_scene_count; +} diff --git a/src/zigbee/switch_cluster.h b/src/zigbee/switch_cluster.h index 89fcac1ee8..2b93edaa90 100644 --- a/src/zigbee/switch_cluster.h +++ b/src/zigbee/switch_cluster.h @@ -18,8 +18,15 @@ typedef struct u16 button_long_press_duration; u8 level_move_rate; u8 binded_mode; + + u8 out_scene_start_offset; + u8 out_scene_count; // 0 disables it + + u16 out_scene_group_id; + u16 scene_recall_time; } zigbee_switch_cluster_config; +#define SWITCH_CLUSTER_ONOFF_CONFIGURATION_ATTRS 14 typedef struct { u8 switch_idx; @@ -29,8 +36,16 @@ typedef struct u8 relay_mode; u8 relay_index; u8 binded_mode; + + u8 out_scene_start_offset; + u8 out_scene_count; // 0 disables it + u8 out_scene_next; + + u16 out_scene_group_id; + u16 scene_recall_time; + button_t * button; - zclAttrInfo_t attr_infos[8]; + zclAttrInfo_t attr_infos[SWITCH_CLUSTER_ONOFF_CONFIGURATION_ATTRS]; u16 multistate_state; zclAttrInfo_t multistate_attr_infos[4]; move_t * level_move; @@ -38,6 +53,6 @@ typedef struct void switch_cluster_add_to_endpoint(zigbee_switch_cluster *cluster, zigbee_endpoint *endpoint); -void switch_cluster_callback_attr_write_trampoline(u8 clusterId); +void switch_cluster_callback_attr_write_trampoline(u8 endpoint, u16 clusterId, zclWriteCmd_t *pWriteReqCmd); #endif diff --git a/zigbee2mqtt/converters/switch_custom.js b/zigbee2mqtt/converters/switch_custom.js index 5856023437..8797f47337 100644 --- a/zigbee2mqtt/converters/switch_custom.js +++ b/zigbee2mqtt/converters/switch_custom.js @@ -88,7 +88,7 @@ const romasku = { name, endpointName, access: "STATE_GET", - lookup: { released: 0, press: 1, long_press: 2, position_on: 3, position_off: 4 }, + lookup: { released: 0, single_press: 1, long_press: 2, position_on: 3, position_off: 4, double_press: 5, single_release: 6 }, cluster: "genMultistateInput", attribute: "presentValue", description: "Action of the switch: 'released' or 'press' or 'long_press'", @@ -124,6 +124,50 @@ const romasku = { description: "State of the network indicator LED", access: "ALL", }), + nextScene:(name, endpointName) => + numeric({ + name, + endpointNames: [endpointName], + cluster: "genOnOffSwitchCfg", + attribute: { ID: 0xff06, type: 0x20 }, // uint8 + description: "Next scene to be recalled", + valueMin: 0, + valueMax: 255, + }), + sceneCount: (name, endpointName) => + numeric({ + name, + endpointNames: [endpointName], + cluster: "genOnOffSwitchCfg", + attribute: { ID: 0xff07, type: 0x20 }, // uint8 + description: "Number of scenes to iterate through", + valueMin: 0, + valueMax: 255, + }), + sceneOffset: (name, endpointName) => + numeric({ + name, + endpointNames: [endpointName], + cluster: "genOnOffSwitchCfg", + attribute: { ID: 0xff08, type: 0x20 }, // uint8 + description: "Offset of the first scene", + }), + sceneGroupId: (name, endpointName) => + numeric({ + name, + endpointNames: [endpointName], + cluster: "genOnOffSwitchCfg", + attribute: { ID: 0xff09, type: 0x21 }, // uint16 + description: "Scene cluster for scene recall commands. 0xffff means infer from bindings", + }), + sceneRecallTime: (name, endpointName) => + numeric({ + name, + endpointNames: [endpointName], + cluster: "genOnOffSwitchCfg", + attribute: { ID: 0xff0a, type: 0x21 }, // uint16 + description: "Recall time for the scene recall commands. 0xffff is default.", + }), deviceConfig: (name, endpointName) => text({ name, @@ -1857,6 +1901,11 @@ const definitions = [ romasku.bindedMode("switch_left_binded_mode", "switch_left"), romasku.longPressDuration("switch_left_long_press_duration", "switch_left"), romasku.levelMoveRate("switch_left_level_move_rate", "switch_left"), + romasku.nextScene("switch_left_next_scene", "switch_left"), + romasku.sceneCount("switch_left_scene_count", "switch_left"), + romasku.sceneOffset("switch_left_scene_offset", "switch_left"), + romasku.sceneGroupId("switch_left_scene_group_id", "switch_left"), + romasku.sceneRecallTime("switch_left_scene_recall_time", "switch_left"), romasku.pressAction("switch_right_press_action", "switch_right"), romasku.switchMode("switch_right_mode", "switch_right"), romasku.switchAction("switch_right_action_mode", "switch_right"), @@ -1865,6 +1914,11 @@ const definitions = [ romasku.bindedMode("switch_right_binded_mode", "switch_right"), romasku.longPressDuration("switch_right_long_press_duration", "switch_right"), romasku.levelMoveRate("switch_right_level_move_rate", "switch_right"), + romasku.nextScene("switch_right_next_scene", "switch_right"), + romasku.sceneCount("switch_right_scene_count", "switch_right"), + romasku.sceneOffset("switch_right_scene_offset", "switch_right"), + romasku.sceneGroupId("switch_right_scene_group_id", "switch_right"), + romasku.sceneRecallTime("switch_right_scene_recall_time", "switch_right"), ], meta: { multiEndpoint: true }, configure: async (device, coordinatorEndpoint, logger) => {