Skip to content

Commit a17dcb2

Browse files
committed
Add Interlock Mode
1 parent 44f74ed commit a17dcb2

10 files changed

Lines changed: 215 additions & 16 deletions

File tree

helper_scripts/make_homed_extension.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,13 @@
101101
"multiPressResetCount": {"type": "number", "min": 0, "max": 255}
102102
}
103103

104+
if relay_cnt > 1:
105+
basic_custom_attrs["interlockMode"] = {"type": "bool", "clusterId": 0x0000, "attributeId": 0xff03, "dataType": 0x10, "action": True}
106+
basic_custom_attrs["interlockDelay"] = {"type": "value", "clusterId": 0x0000, "attributeId": 0xff04, "dataType": 0x21, "action": True}
107+
basic_exposes.extend(["interlockMode", "interlockDelay"])
108+
basic_options["interlockMode"] = {"type": "toggle"}
109+
basic_options["interlockDelay"] = {"type": "number", "min": 0, "max": 5000}
110+
104111
if has_dedicated_net_led:
105112
basic_custom_attrs["networkIndicator"] = {"type": "bool", "clusterId": 0x0000, "attributeId": 0xff01, "dataType": 0x10, "action": True}
106113
basic_exposes.append("networkIndicator")

helper_scripts/templates/switch_custom.js.jinja

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,29 @@ const romasku = {
186186
valueMax: 255,
187187
entityCategory: "config",
188188
}),
189+
interlockMode: (name, endpointName) =>
190+
binary({
191+
name,
192+
endpointName,
193+
valueOn: ["ON", 1],
194+
valueOff: ["OFF", 0],
195+
cluster: "genBasic",
196+
attribute: {ID: 0xff03, type: 0x10}, // Boolean
197+
description: "Enable interlock mode for relays",
198+
access: "ALL",
199+
entityCategory: "config",
200+
}),
201+
interlockDelay: (name, endpointName) =>
202+
numeric({
203+
name,
204+
endpointNames: [endpointName],
205+
cluster: "genBasic",
206+
attribute: { ID: 0xff04, type: 0x21 }, // uint16
207+
description: "Delay in milliseconds between turning off a relay and turning on the next in interlock mode",
208+
valueMin: 0,
209+
valueMax: 5000,
210+
entityCategory: "config",
211+
}),
189212
deviceConfig: (name, endpointName) =>
190213
text({
191214
name,
@@ -418,6 +441,10 @@ const definitions = [
418441
{% set first_endpoint = device.switchNames[0] if device.switchNames else (device.relayNames[0] if device.relayNames else (device.coverSwitchNames[0] if device.coverSwitchNames else device.coverNames[0])) %}
419442
romasku.deviceConfig("device_config", "{{first_endpoint}}"),
420443
romasku.multiPressResetCount("multi_press_reset_count", "{{first_endpoint}}"),
444+
{% if device.relayNames and device.relayNames | length > 1 %}
445+
romasku.interlockMode("interlock_mode", "{{first_endpoint}}"),
446+
romasku.interlockDelay("interlock_delay", "{{first_endpoint}}"),
447+
{% endif %}
421448
{% if device.has_dedicated_net_led %}
422449
romasku.networkIndicator("network_led", "{{first_endpoint}}"),
423450
{% endif%}

helper_scripts/templates/zha_quirk.py.jinja

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,20 @@ class CustomBasicCluster(CustomCluster, Basic):
121121
is_manufacturer_specific=False,
122122
)
123123

124+
interlock_mode = ZCLAttributeDef(
125+
id=0xff03,
126+
type=t.Bool,
127+
access="rw",
128+
is_manufacturer_specific=False,
129+
)
130+
131+
interlock_delay = ZCLAttributeDef(
132+
id=0xff04,
133+
type=t.uint16_t,
134+
access="rw",
135+
is_manufacturer_specific=False,
136+
)
137+
124138

125139
class RelayIndicatorMode(t.enum8):
126140
Same = 0x00
@@ -489,6 +503,30 @@ for config in CONFIGS:
489503
)
490504
)
491505

506+
if relay_cnt > 1:
507+
builder = (
508+
builder
509+
.switch(
510+
CustomBasicCluster.AttributeDefs.interlock_mode.name,
511+
CustomBasicCluster.cluster_id,
512+
translation_key="interlock_mode",
513+
fallback_name="Interlock mode",
514+
endpoint_id=1,
515+
entity_type=EntityType.CONFIG,
516+
)
517+
.number(
518+
CustomBasicCluster.AttributeDefs.interlock_delay.name,
519+
CustomBasicCluster.cluster_id,
520+
translation_key="interlock_delay",
521+
fallback_name="Interlock delay",
522+
min_value=0,
523+
max_value=5000,
524+
step=1,
525+
endpoint_id=1,
526+
entity_type=EntityType.CONFIG,
527+
)
528+
)
529+
492530
if has_dedicated_net_led:
493531
builder = (
494532
builder

src/telink/hal/zigbee_zcl.c

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ static hal_zigbee_endpoint *hal_endpoints = NULL;
2626
static uint8_t hal_endpoints_cnt = 0;
2727
static hal_attribute_change_callback_t attribute_change_callback = NULL;
2828
static hal_zcl_activity_callback_t zcl_activity_callback = NULL;
29+
static volatile bool in_zcl_callback = false;
2930

3031
static cluster_registerFunc_t get_register_func_by_cluster_id(u16 cluster_id) {
3132
if (cluster_id == ZCL_CLUSTER_GEN_BASIC) {
@@ -70,22 +71,25 @@ static status_t cmd_callback(u8 endpoint, u16 clusterId, u8 cmdId,
7071
hal_zigbee_cluster *cluster = hal_zigbee_find_cluster(
7172
hal_endpoints, hal_endpoints_cnt, endpoint, clusterId);
7273

74+
status_t ret = ZCL_STA_SUCCESS;
7375
if (cluster && cluster->cmd_callback) {
76+
in_zcl_callback = true;
7477
hal_zigbee_cmd_result_t status = cluster->cmd_callback(
7578
endpoint, clusterId, cmdId, cmdPayload, cmdPayloadLen);
79+
in_zcl_callback = false;
7680
if (status == HAL_ZIGBEE_CMD_PROCESSED) {
77-
return ZCL_STA_SUCCESS;
81+
ret = ZCL_STA_SUCCESS;
7882
} else if (status == HAL_ZIGBEE_INVALID_VALUE) {
79-
return ZCL_STA_INVALID_VALUE;
83+
ret = ZCL_STA_INVALID_VALUE;
8084
} else if (status == HAL_ZIGBEE_MALFORMED_COMMAND) {
81-
return ZCL_STA_MALFORMED_COMMAND;
85+
ret = ZCL_STA_MALFORMED_COMMAND;
8286
} else if (status == HAL_ZIGBEE_ACTION_DENIED) {
83-
return ZCL_STA_ACTION_DENIED;
87+
ret = ZCL_STA_ACTION_DENIED;
8488
} else if (status == HAL_ZIGBEE_CMD_SKIPPED) {
85-
return ZCL_STA_UNSUP_CLUSTER_COMMAND;
89+
ret = ZCL_STA_UNSUP_CLUSTER_COMMAND;
8690
}
8791
}
88-
return(ZCL_STA_SUCCESS);
92+
return ret;
8993
}
9094

9195
static zclIncoming_t *cmd_incoming_from_addr_info(zclIncomingAddrInfo_t *pAddrInfo) {
@@ -148,12 +152,14 @@ static void zcl_incoming_message_callback(zclIncoming_t *pInHdlrMsg) {
148152
if (attribute_change_callback == NULL) {
149153
return;
150154
}
155+
in_zcl_callback = true;
151156
zclWriteCmd_t *writeCmd = (zclWriteCmd_t *)pInHdlrMsg->attrCmd;
152157
for (u8 i = 0; i < writeCmd->numAttr; i++) {
153158
attribute_change_callback(pInHdlrMsg->msg->indInfo.dst_ep,
154159
pInHdlrMsg->msg->indInfo.cluster_id,
155160
writeCmd->attrList[i].attrID);
156161
}
162+
in_zcl_callback = false;
157163
}
158164
}
159165

@@ -237,7 +243,9 @@ void telink_zigbee_hal_zcl_init(hal_zigbee_endpoint *endpoints,
237243

238244
void hal_zigbee_notify_attribute_changed(uint8_t endpoint, uint16_t cluster_id,
239245
uint16_t attribute_id) {
240-
report_handler(); // Trigger reporting if needed
246+
if (!in_zcl_callback) {
247+
report_handler(); // Trigger reporting if needed
248+
}
241249
}
242250

243251
hal_zigbee_status_t hal_zigbee_send_cmd_to_bindings(const hal_zigbee_cmd *cmd) {

src/zigbee/basic_cluster.c

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ uint8_t powerSource = POWER_SOURCE_MAINS_1_PHASE; // 0x01 default
2727
const uint16_t cluster_revision = 0x01;
2828
DEF_STR(STRINGIFY_VALUE(VERSION_STR), swBuildId);
2929
extern network_indicator_t network_indicator;
30+
extern zigbee_basic_cluster basic_cluster;
3031

3132
void basic_cluster_store_attrs_to_nv();
3233
void basic_cluster_load_attrs_from_nv();
@@ -86,14 +87,18 @@ void basic_cluster_add_to_endpoint(zigbee_basic_cluster *cluster,
8687
ATTR_WRITABLE, device_config_str);
8788
SETUP_ATTR(12, ZCL_ATTR_BASIC_MULTI_PRESS_RESET_COUNT, ZCL_DATA_TYPE_UINT8,
8889
ATTR_WRITABLE, g_multi_press_reset_count);
90+
SETUP_ATTR(13, ZCL_ATTR_BASIC_INTERLOCK_MODE, ZCL_DATA_TYPE_BOOLEAN,
91+
ATTR_WRITABLE, cluster->interlock_mode);
92+
SETUP_ATTR(14, ZCL_ATTR_BASIC_INTERLOCK_DELAY, ZCL_DATA_TYPE_UINT16,
93+
ATTR_WRITABLE, cluster->interlock_delay_ms);
8994
if (network_indicator.has_dedicated_led) {
90-
SETUP_ATTR(13, ZCL_ATTR_BASIC_STATUS_LED_STATE, ZCL_DATA_TYPE_BOOLEAN,
95+
SETUP_ATTR(15, ZCL_ATTR_BASIC_STATUS_LED_STATE, ZCL_DATA_TYPE_BOOLEAN,
9196
ATTR_WRITABLE, network_indicator.manual_state_when_connected);
9297
}
9398

9499
endpoint->clusters[endpoint->cluster_count].cluster_id = ZCL_CLUSTER_BASIC;
95100
endpoint->clusters[endpoint->cluster_count].attribute_count =
96-
network_indicator.has_dedicated_led ? 14 : 13;
101+
network_indicator.has_dedicated_led ? 16 : 15;
97102
endpoint->clusters[endpoint->cluster_count].attributes = cluster->attr_infos;
98103
endpoint->clusters[endpoint->cluster_count].is_server = 1;
99104
endpoint->cluster_count++;
@@ -108,13 +113,17 @@ void basic_cluster_add_to_endpoint(zigbee_basic_cluster *cluster,
108113

109114
typedef struct {
110115
uint8_t network_led_on;
116+
uint8_t interlock_mode;
117+
uint16_t interlock_delay_ms;
111118
} zigbee_basic_cluster_config;
112119

113120
static zigbee_basic_cluster_config nv_config_buffer;
114121

115122
void basic_cluster_store_attrs_to_nv() {
116123
nv_config_buffer.network_led_on =
117124
network_indicator.manual_state_when_connected;
125+
nv_config_buffer.interlock_mode = basic_cluster.interlock_mode;
126+
nv_config_buffer.interlock_delay_ms = basic_cluster.interlock_delay_ms;
118127

119128
hal_nvm_write(NV_ITEM_BASIC_CLUSTER_DATA, sizeof(zigbee_basic_cluster_config),
120129
(uint8_t *)&nv_config_buffer);
@@ -126,8 +135,18 @@ void basic_cluster_load_attrs_from_nv() {
126135
(uint8_t *)&nv_config_buffer);
127136

128137
if (st != HAL_NVM_SUCCESS) {
138+
basic_cluster.interlock_mode = 0;
139+
basic_cluster.interlock_delay_ms = 100;
129140
return;
130141
}
131142
network_indicator.manual_state_when_connected =
132143
nv_config_buffer.network_led_on;
144+
basic_cluster.interlock_mode = nv_config_buffer.interlock_mode;
145+
basic_cluster.interlock_delay_ms = nv_config_buffer.interlock_delay_ms;
146+
147+
// In case NVM was blank (0xFF)
148+
if (basic_cluster.interlock_delay_ms == 0xFFFF) {
149+
basic_cluster.interlock_mode = 0;
150+
basic_cluster.interlock_delay_ms = 100;
151+
}
133152
}

src/zigbee/basic_cluster.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ typedef struct {
99
uint8_t deviceEnable;
1010
char manuName[32];
1111
char modelId[32];
12-
hal_zigbee_attribute attr_infos[14];
12+
uint8_t interlock_mode;
13+
uint16_t interlock_delay_ms;
14+
hal_zigbee_attribute attr_infos[16];
1315
} zigbee_basic_cluster;
1416

1517
void basic_cluster_add_to_endpoint(zigbee_basic_cluster *cluster,

src/zigbee/consts.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
#define ZCL_ATTR_BASIC_DEVICE_CONFIG 0xff00
4545
#define ZCL_ATTR_BASIC_STATUS_LED_STATE 0xff01
4646
#define ZCL_ATTR_BASIC_MULTI_PRESS_RESET_COUNT 0xff02
47+
#define ZCL_ATTR_BASIC_INTERLOCK_MODE 0xff03
48+
#define ZCL_ATTR_BASIC_INTERLOCK_DELAY 0xff04
4749

4850
// Power Configuration cluster
4951

src/zigbee/relay_cluster.c

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
#include "device_config/nvm_items.h"
55
#include "hal/nvm.h"
66
#include "hal/printf_selector.h"
7+
#include "basic_cluster.h"
8+
9+
extern zigbee_basic_cluster basic_cluster;
10+
extern uint8_t relay_clusters_cnt;
711

812
hal_zigbee_cmd_result_t relay_cluster_callback(zigbee_relay_cluster *cluster,
913
uint8_t command_id,
@@ -58,6 +62,10 @@ void relay_cluster_add_to_endpoint(zigbee_relay_cluster *cluster,
5862
cluster->endpoint = endpoint->endpoint;
5963
relay_cluster_load_attrs_from_nv(cluster);
6064

65+
cluster->delayed_on_task.arg = cluster;
66+
hal_tasks_init(&cluster->delayed_on_task);
67+
cluster->pending_on = 0;
68+
6169
cluster->relay->callback_param = cluster;
6270
cluster->relay->on_change = (relay_callback_t)relay_cluster_on_relay_change;
6371

@@ -181,19 +189,65 @@ void sync_indicator_led(zigbee_relay_cluster *cluster) {
181189
ZCL_ATTR_ONOFF_INDICATOR_STATE);
182190
}
183191

192+
static void relay_cluster_delayed_on_handler(zigbee_relay_cluster *cluster) {
193+
cluster->pending_on = 0;
194+
relay_on(cluster->relay);
195+
sync_indicator_led(cluster);
196+
}
197+
184198
void relay_cluster_on(zigbee_relay_cluster *cluster) {
199+
if (cluster->pending_on) {
200+
return;
201+
}
202+
hal_tasks_unschedule(&cluster->delayed_on_task);
203+
cluster->pending_on = 0;
204+
205+
if (basic_cluster.interlock_mode && relay_clusters_cnt > 1) {
206+
bool others_were_active = false;
207+
for (int i = 0; i < 10; i++) {
208+
if (relay_cluster_by_endpoint[i] != NULL &&
209+
relay_cluster_by_endpoint[i] != cluster) {
210+
211+
if (relay_cluster_by_endpoint[i]->relay->on ||
212+
relay_cluster_by_endpoint[i]->pending_on) {
213+
214+
relay_cluster_off(relay_cluster_by_endpoint[i]);
215+
others_were_active = true;
216+
}
217+
}
218+
}
219+
220+
if (others_were_active && basic_cluster.interlock_delay_ms > 0) {
221+
cluster->pending_on = 1;
222+
cluster->delayed_on_task.handler = (task_handler_t)relay_cluster_delayed_on_handler;
223+
hal_tasks_schedule(&cluster->delayed_on_task, basic_cluster.interlock_delay_ms);
224+
return;
225+
}
226+
}
227+
185228
relay_on(cluster->relay);
186229
sync_indicator_led(cluster);
187230
}
188231

189232
void relay_cluster_off(zigbee_relay_cluster *cluster) {
233+
hal_tasks_unschedule(&cluster->delayed_on_task);
234+
if (cluster->pending_on) {
235+
cluster->pending_on = 0;
236+
uint8_t val = 0;
237+
hal_zigbee_send_report_attr(cluster->endpoint, ZCL_CLUSTER_ON_OFF,
238+
ZCL_ATTR_ONOFF, ZCL_DATA_TYPE_BOOLEAN,
239+
&val, 1);
240+
}
190241
relay_off(cluster->relay);
191242
sync_indicator_led(cluster);
192243
}
193244

194245
void relay_cluster_toggle(zigbee_relay_cluster *cluster) {
195-
relay_toggle(cluster->relay);
196-
sync_indicator_led(cluster);
246+
if (cluster->relay->on || cluster->pending_on) {
247+
relay_cluster_off(cluster);
248+
} else {
249+
relay_cluster_on(cluster);
250+
}
197251
}
198252

199253
void relay_cluster_on_relay_change(zigbee_relay_cluster *cluster,

src/zigbee/relay_cluster.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ typedef struct {
1616
relay_t * relay;
1717
led_t * indicator_led;
1818
uint8_t indicator_state;
19+
hal_task_t delayed_on_task;
20+
uint8_t pending_on;
1921
} zigbee_relay_cluster;
2022

2123
void relay_cluster_add_to_endpoint(zigbee_relay_cluster *cluster,

0 commit comments

Comments
 (0)