3.3 Z-Stack 事件的应用
本节导读
上一节我们知道了事件处理函数的"标准模板",也知道了事件有三个来源:定时器、系统消息、外设中断。
这一节我们把这三个来源挨个"实战"一遍——每种事件怎么产生、怎么处理、代码怎么写,看完你就能在自己的工程里"制造事件"了。
原站本节内容已失效,本站按 Z-Stack 3.0 源码与教学实践重写,例程以 SampleSwitch 工程为准。
一、定时器事件:让代码"到点上班"
这是最常用的一种事件——想让某段代码每隔固定时间执行一次,就用它。
触发 API:osal_start_timerEx()
uint8 osal_start_timerEx(uint8 task_id, uint16 event_id, uint32 timeout_value);| 参数 | 含义 | 大白话 |
|---|---|---|
task_id | 事件属于哪个任务 | 工单派给哪个部门 |
event_id | 事件类型 | 工单内容是什么 |
timeout_value | 多少毫秒后触发 | 几点几分开工 |
实战:3 秒一次的"报时钟"
在应用层初始化函数末尾添加:

void zclSampleSw_Init(byte task_id) {
// ……其他初始化……
// 3 秒后触发 SAMPLEAPP_TEST_EVT 事件
osal_start_timerEx(
zclSampleSw_TaskID, // 派给应用层任务
SAMPLEAPP_TEST_EVT, // 事件:测试事件
3000 // 3000ms 后触发
);
}在事件处理函数里处理它:
uint16 zclSampleSw_event_loop(uint8 task_id, uint16 events) {
if (events & SAMPLEAPP_TEST_EVT) {
printf("Hello World!\r\n"); // 到点了,报个时
// ★ 干完活再设一个 3 秒的闹钟 → 周而复始 ★
osal_start_timerEx(zclSampleSw_TaskID, SAMPLEAPP_TEST_EVT, 3000);
return (events ^ SAMPLEAPP_TEST_EVT); // 清除已处理事件
}
return 0;
}注意最后那行 osal_start_timerEx(...) 又在处理函数里调了一次——事件处理完,再给自己定个 3 秒后的闹钟。于是:
第 0 秒 初始化时设闹钟
第 3 秒 触发事件 → 打印 → 再设闹钟
第 6 秒 触发事件 → 打印 → 再设闹钟
第 9 秒 ……无限循环……生活类比:这就是报时鸟——每次报完时,都会自己上好发条,3 秒后再报一次。**"处理完再触发自己"**是 Z-Stack 里做周期任务的标准套路。
二、系统消息事件:任务之间"传纸条"
不同任务之间要通信怎么办?发消息。OSAL 用消息队列实现任务间通信。
消息的标志:SYS_EVENT_MSG
事件集合的最高位(0x8000)是系统事件标志,其中最重要的就是 SYS_EVENT_MSG(系统消息事件):
#define SYS_EVENT_MSG 0x8000只要某任务的消息队列里有消息在等,OSAL 就会给它置上 SYS_EVENT_MSG 事件。
消息的收发三件套
// ① 发送消息:把消息发给指定任务
uint8 osal_msg_send(uint8 destination_task, uint8 *msg_ptr);
// ② 接收消息:从自己的消息队列里取出一条
uint8 *osal_msg_receive(uint8 task_id);
// ③ 释放消息:用完的消息必须还回去(重要!)
uint8 osal_msg_deallocate(uint8 *msg_ptr);实战:收到消息就点亮 LED
在事件处理函数中处理系统消息:
uint16 zclSampleSw_event_loop(uint8 task_id, uint16 events) {
// 处理系统消息事件
if (events & SYS_EVENT_MSG) {
uint8 *msg;
// ① 从消息队列取出一条消息
msg = osal_msg_receive(zclSampleSw_TaskID);
while (msg) { // 有消息就处理
// 根据消息的第一个字节判断消息类型
switch (*msg) {
case MY_LED_MSG: // 自定义消息:点亮 LED
HalLedSet(HAL_LED_1, HAL_LED_MODE_ON);
break;
// ……其他消息类型……
}
osal_msg_deallocate(msg); // ② 用完必须释放!
msg = osal_msg_receive(zclSampleSw_TaskID); // 再取一条
}
return (events ^ SYS_EVENT_MSG); // 清除系统消息事件
}
return 0;
}消息处理的铁律:
| 步骤 | 必须做吗 | 为什么 |
|---|---|---|
取消息 osal_msg_receive | 是 | 消息不会自己跑来 |
用完释放 osal_msg_deallocate | 是! | 不释放 = 内存泄漏,跑几天就崩 |
| 循环取到 NULL | 是 | 队列可能一次来好几条 |
生活类比:消息就是同事递来的纸条。收到纸条要打开看(接收)、看完要处理(switch)、看完必须扔掉(释放)——不然桌上堆满纸条,内存(桌面)就爆了。
三、外设事件:按键按一下就有反应
按键、串口这些外设产生的事件,走的是 HAL 层。HAL 任务会定时轮询按键,发现按下就通过回调或消息通知应用层。
在 SampleSwitch 工程里,应用层的按键处理函数长这样:
static void zclSampleSw_HandleKeys(byte shift, byte keys) {
// keys 是按位表示的按键状态,比如 HAL_KEY_SW_1 = 第一个按键
if (keys & HAL_KEY_SW_1) {
HalLedSet(HAL_LED_1, HAL_LED_MODE_TOGGLE); // 按下 S3 → 翻转 LED1
}
}按键事件的处理入口在事件处理函数中:
uint16 zclSampleSw_event_loop(uint8 task_id, uint16 events) {
// 按键事件由 HAL 通过系统消息送达
if (events & SYS_EVENT_MSG) {
uint8 *msg;
msg = osal_msg_receive(zclSampleSw_TaskID);
while (msg) {
if (*msg == KEY_CHANGE) { // 按键变化消息
zclSampleSw_HandleKeys(0, *(uint8 *)(msg + 1)); // 处理按键
}
osal_msg_deallocate(msg);
msg = osal_msg_receive(zclSampleSw_TaskID);
}
return (events ^ SYS_EVENT_MSG);
}
return 0;
}生活类比:门铃响了(按键中断)→ 门卫(HAL)记下"有人按铃"写成纸条(消息)→ 塞进你家信箱(消息队列)→ 你(应用层)看到信箱有信(SYS_EVENT_MSG)→ 拆信看是谁按的(判断按键)→ 决定开不开门(处理)。
四、事件同时来好几个怎么办?
一个任务的事件集合是一堆开关(二进制位),可能同时亮好几个。处理函数里按顺序逐个判断:
uint16 zclSampleSw_event_loop(uint8 task_id, uint16 events) {
if (events & SAMPLEAPP_TEST_EVT) { // ① 先处理定时器事件
printf("Hello World!\r\n");
osal_start_timerEx(zclSampleSw_TaskID, SAMPLEAPP_TEST_EVT, 3000);
return (events ^ SAMPLEAPP_TEST_EVT);
}
if (events & SYS_EVENT_MSG) { // ② 再处理系统消息
// ……处理消息……
return (events ^ SYS_EVENT_MSG);
}
return 0;
}注意:处理完一类事件就 return——没处理完的(另一类)由返回值带回去,下一轮轮询继续。一次只处理一类,但保证每类都会被处理到,这就是"分轮处理"。
五、小结
| 事件来源 | 触发 API / 机制 | 处理要点 |
|---|---|---|
| 定时器 | osal_start_timerEx() | 处理完再触发自己 = 周期任务 |
| 系统消息 | osal_msg_send() / osal_msg_receive() | 用完必须 osal_msg_deallocate() 释放 |
| 外设按键 | HAL 轮询 → 消息 → SYS_EVENT_MSG | 判断 keys & HAL_KEY_SW_x |
| 多个同时 | 处理函数里逐个 if 判断 | 处理一类就 return,没处理完的下轮继续 |
一句话总结:Z-Stack 的事件应用 = "闹钟"(定时器)、"纸条"(消息)、"门铃"(外设)三种工单,都会写进任务的待办箱;处理函数按"判断 → 处理 → 清除返回"的模板逐个消化。会设闹钟、会传纸条、会听门铃,你就掌握了 Z-Stack 应用开发的一半。
内容来源:紫甲 IoT《ZigBee 3.0 开发指南》第三部分·3.3 节(原站已失效),本站按 Z-Stack 3.0 源码与教学实践重写;原站链接