Skip to content

3.3 Z-Stack 事件的应用 ​

本节导读

上一节我们知道了事件处理函数的"标准模板",也知道了事件有三个来源:定时器、系统消息、外设中断。

这一节我们把这三个来源挨个"实战"一遍——每种事件怎么产生、怎么处理、代码怎么写,看完你就能在自己的工程里"制造事件"了。

原站本节内容已失效,本站按 Z-Stack 3.0 源码与教学实践重写,例程以 SampleSwitch 工程为准。

一、定时器事件:让代码"到点上班" ​

这是最常用的一种事件——想让某段代码每隔固定时间执行一次,就用它。

触发 API:osal_start_timerEx() ​

c
uint8 osal_start_timerEx(uint8 task_id, uint16 event_id, uint32 timeout_value);
参数含义大白话
task_id事件属于哪个任务工单派给哪个部门
event_id事件类型工单内容是什么
timeout_value多少毫秒后触发几点几分开工

实战:3 秒一次的"报时钟" ​

在应用层初始化函数末尾添加:

添加 osal_start_timerEx 后的效果

c
void zclSampleSw_Init(byte task_id) {
    // ……其他初始化……

    // 3 秒后触发 SAMPLEAPP_TEST_EVT 事件
    osal_start_timerEx(
        zclSampleSw_TaskID,     // 派给应用层任务
        SAMPLEAPP_TEST_EVT,     // 事件:测试事件
        3000                    // 3000ms 后触发
    );
}

在事件处理函数里处理它:

c
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 秒后的闹钟。于是:

text
第 0 秒  初始化时设闹钟
第 3 秒  触发事件 → 打印 → 再设闹钟
第 6 秒  触发事件 → 打印 → 再设闹钟
第 9 秒  ……无限循环……

生活类比:这就是报时鸟——每次报完时,都会自己上好发条,3 秒后再报一次。**"处理完再触发自己"**是 Z-Stack 里做周期任务的标准套路。

二、系统消息事件:任务之间"传纸条" ​

不同任务之间要通信怎么办?发消息。OSAL 用消息队列实现任务间通信。

消息的标志:SYS_EVENT_MSG ​

事件集合的最高位(0x8000)是系统事件标志,其中最重要的就是 SYS_EVENT_MSG(系统消息事件):

c
#define SYS_EVENT_MSG 0x8000

只要某任务的消息队列里有消息在等,OSAL 就会给它置上 SYS_EVENT_MSG 事件。

消息的收发三件套 ​

c
// ① 发送消息:把消息发给指定任务
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 ​

在事件处理函数中处理系统消息:

c
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 工程里,应用层的按键处理函数长这样:

c
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
    }
}

按键事件的处理入口在事件处理函数中:

c
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)→ 拆信看是谁按的(判断按键)→ 决定开不开门(处理)。

四、事件同时来好几个怎么办? ​

一个任务的事件集合是一堆开关(二进制位),可能同时亮好几个。处理函数里按顺序逐个判断:

c
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 源码与教学实践重写;原站链接

aioteer · 物联网专业课程群

💬AI 伴学配置