javnson 发表于 2022-5-10 09:56

【涂鸦BK7231N】样例代码精读分析

<p cid="n0" mdtype="paragraph">今天来学习一下tuya的例程。其基本的软件框架如下:</p>

<figure cid="n2" mdtype="table">
<table>
        <thead>
                <tr cid="n3" mdtype="table_row">
                        <th>文件</th>
                        <th>概要</th>
                </tr>
        </thead>
        <tbody>
                <tr cid="n6" mdtype="table_row">
                        <td>tuya_device.c</td>
                        <td>用来配置wifi连接</td>
                </tr>
                <tr cid="n9" mdtype="table_row">
                        <td>dp_process.c</td>
                        <td>用来处理云命令</td>
                </tr>
                <tr cid="n12" mdtype="table_row">
                        <td>light_system.c</td>
                        <td>作为云命令的底层</td>
                </tr>
        </tbody>
</table>
</figure>

<p cid="n15" mdtype="paragraph">为了更加清晰地让大家观察到系统执行的内部架构,我们首先循着主线来观察。</p>

<p cid="n16" mdtype="paragraph">在<code>dp_process.h</code>文件中我们可以找到如下这个消息响应函数:</p>

<pre cid="n17" lang="C++" mdtype="fences" spellcheck="false">
&nbsp;VOID_T deal_dp_proc(IN CONST TY_OBJ_DP_S *root)
&nbsp;{
&nbsp; &nbsp; &nbsp;OPERATE_RET op_ret = OPRT_OK;
&nbsp;​
&nbsp; &nbsp; &nbsp;UCHAR_T dpid;
&nbsp; &nbsp; &nbsp;dpid = root-&gt;dpid;
&nbsp; &nbsp; &nbsp;PR_DEBUG(&quot;dpid:%d&quot;, dpid);
&nbsp;​
&nbsp; &nbsp; &nbsp;switch(dpid) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;case DPID_LIGHT_SWITCH:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;if (root-&gt;value.dp_bool == TRUE) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;op_ret = set_light_status(LIGHT_ON);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;if (op_ret != OPRT_OK) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;PR_ERR(&quot;dp process set light status error, %d&quot;, op_ret);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;return;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;}
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;} else {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;op_ret = set_light_status(LIGHT_OFF);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;if (op_ret != OPRT_OK) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;PR_ERR(&quot;dp process set light status error, %d&quot;, op_ret);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;return;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;}
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;}
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;/* update device current status to cloud */
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;update_all_dp();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;break;
&nbsp;​
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;default :
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;break;
&nbsp; &nbsp;}
&nbsp;}</pre>

<p cid="n18" mdtype="paragraph">在本项开关灯例程中,我们注册了一项消息,名为:<code>DPID_LIGHT_SWITCH</code>,在头文件中被定义为20。</p>

<p cid="n19" mdtype="paragraph">当发生这个消息的时候将会首先检测当前灯的状态,然后根据等的状态来调用函数<code>set_light_status()</code>来设定灯的开关。</p>

<p cid="n20" mdtype="paragraph">最终同步当前设备的状态到云端,使用函数<code>update_all_dp</code>。</p>

<p cid="n21" mdtype="paragraph">其中,<code>set_light_status()</code>在文件<code>light_system.c</code>中给出了定义,具体实现如下:</p>

<pre cid="n22" lang="C++" mdtype="fences" spellcheck="false">
&nbsp;OPERATE_RET set_light_status(LED_STATUS_E status)
&nbsp;{
&nbsp; &nbsp; &nbsp;OPERATE_RET op_ret = OPRT_OK;
&nbsp;​
&nbsp; &nbsp; &nbsp;if (status == LIGHT_ON) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;op_ret = light_on();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;if (op_ret != OPRT_OK) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;return op_ret;
&nbsp; &nbsp; &nbsp; &nbsp;}
&nbsp; &nbsp;} else {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;light_off();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;if (op_ret != OPRT_OK) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;return op_ret;
&nbsp; &nbsp; &nbsp; &nbsp;}
&nbsp; &nbsp;}
&nbsp;​
&nbsp; &nbsp; &nbsp;return op_ret;
&nbsp;}</pre>

<p cid="n23" mdtype="paragraph">这个函数作为对灯的操作的底层。其中调用的<code>light_off();</code>和<code>light_on();</code>是这个底层函数的具体实现,两个函数都被定义成了<code>static</code>的函数,作为局部函数来用,以防用户错误调用。</p>

<p cid="n24" mdtype="paragraph">其中通过函数<code>tuya_gpio_write</code>来实现对于特定IO口的操作。在GPIO的定义中,我们可以看到链接灯的引脚是<code>TY_GPIOA_16</code>。这个函数在库函数中给出,但是具体源码不表,其中应该也是通过调整寄存器实现对于GPIO的操作。</p>

<p cid="n25" mdtype="paragraph">在明确底层之后,我们需要回过头看一下,与云端的上报函数<code>update_all_dp</code>。其基本得思路概括如下:</p>

<pre cid="n26" lang="text" mdtype="fences" spellcheck="false">
&nbsp;// 检测是不是及已经建立连接
&nbsp;​
&nbsp;// 创建、初始化DP对象(上报对象)
&nbsp;​
&nbsp;// 然后创建一个DP对象
&nbsp;​
&nbsp;// 上传DP对象
&nbsp;dev_report_dp_json_async();
&nbsp;​
&nbsp;// 释放DP对象</pre>

<p cid="n27" mdtype="paragraph">DP就是一个数据上报的基本结构体其定义如下:</p>

<pre cid="n28" lang="C++" mdtype="fences" spellcheck="false">
&nbsp;/**
&nbsp; * @brief Definition of structured dp
&nbsp; */
&nbsp;typedef struct {
&nbsp; &nbsp; &nbsp;/** dp id */
&nbsp; &nbsp; &nbsp;BYTE_T dpid;
&nbsp; &nbsp; &nbsp;/** dp type, see DP_PROP_TP_E */
&nbsp; &nbsp; &nbsp;DP_PROP_TP_E type;
&nbsp; &nbsp; &nbsp;/** dp value, see TY_OBJ_DP_VALUE_U */
&nbsp; &nbsp; &nbsp;TY_OBJ_DP_VALUE_U value;
&nbsp; &nbsp; &nbsp;/** dp happen time. if 0, mean now */
&nbsp; &nbsp; &nbsp;UINT_T time_stamp;
&nbsp;}TY_OBJ_DP_S;</pre>

<p cid="n29" mdtype="paragraph">其中,保存数据的单元是<code>TY_OBJ_DP_VALUE_U</code>用一个union来定义。其中通过<code>type</code>字段来指明具体使用的类型。</p>

<pre cid="n30" lang="C++" mdtype="fences" spellcheck="false">
&nbsp;/**
&nbsp; * @brief tuya sdk dp value union
&nbsp; */
&nbsp;typedef union {
&nbsp; &nbsp; &nbsp;INT_T dp_value; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // valid when dp type is value
&nbsp; &nbsp; &nbsp;UINT_T dp_enum; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // valid when dp type is enum
&nbsp; &nbsp; &nbsp;CHAR_T *dp_str; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // valid when dp type is str
&nbsp; &nbsp; &nbsp;BOOL_T dp_bool; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // valid when dp type is bool
&nbsp; &nbsp; &nbsp;UINT_T dp_bitmap; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // valid when dp type is bitmap
&nbsp;}TY_OBJ_DP_VALUE_U;</pre>

<p cid="n31" mdtype="paragraph">看完了主要逻辑,接下来要观察一下通信的部分,这一部分在<code>tuya_device.c</code>中实现。其中响应了在手机端控制的按键消息。</p>

<p cid="n32" mdtype="paragraph">在函数<code>wifi_key_process</code>中实现了这一按键相应的功能。实现逻辑与<code>dp_process</code>中相同。</p>

<p cid="n33" mdtype="paragraph">在<code>wifi_key_init</code>函数中注册了这一消息响应函数,可以看到:</p>

<pre cid="n34" lang="C++" mdtype="fences" spellcheck="false">
&nbsp;key_def.call_back = wifi_key_process;</pre>

<p cid="n35" mdtype="paragraph">而这个函数会在系统初始化过程中被调用,从而实现消息注册。</p>

<p cid="n36" mdtype="paragraph">综上所述,我们可以概括一下如果需要实现一项基于tuya的项目需要经历的步骤:</p>

<blockquote cid="n37" mdtype="blockquote">
<ul cid="n38" data-mark="+" mdtype="list">
        <li cid="n39" mdtype="list_item">
        <p cid="n40" mdtype="paragraph">注册特定事件触发的消息及其响应函数</p>
        </li>
        <li cid="n41" mdtype="list_item">
        <p cid="n42" mdtype="paragraph">完成事件的底层逻辑</p>
        </li>
        <li cid="n43" mdtype="list_item">
        <p cid="n44" mdtype="paragraph">在响应函数中调用其底层逻辑</p>
        </li>
</ul>
</blockquote>

<p cid="n45" mdtype="paragraph">而底层逻辑的细节可以在tuya给出的sdk库中找到。</p>

<p cid="n46" mdtype="paragraph">以上,共同学习,共同进步!</p>

lugl4313820 发表于 2022-5-10 16:27

分析的很到位,其实涂鸦的最终目的是不用开发者写代码。

javnson 发表于 2022-5-10 23:01

<p>确实确实,对不起有点职业病了,哈哈哈哈哈哈哈哈哈。</p>

<p>但是对于嵌入式工程师其实可以学习一下它的内部逻辑~这种面向对象的思想在嵌入式系统中的应用,哈哈哈哈哈哈哈。</p>

wangerxian 发表于 2022-5-11 14:19

<p>涂鸦主要是给非电子开发者用的,非常容易上手。</p>
页: [1]
查看完整版本: 【涂鸦BK7231N】样例代码精读分析