本文主要是天猫蓝牙Mesh开发——天猫精灵控制智能灯的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
Windows版本 windows11 Ubuntu版本 Ubuntu22.04的64位版本 VMware® Workstation 16 Pro 16.2.3 build-19376536 终端软件 MobaXterm(Professional Edition v23.0 Build 5042 (license))
点击查看本文参考资料
点击查看相关文件下载
一、控制灯的开关 1. 精灵事件 1.1 genie_event() 我们找到这个函数,它定义在SDK源码顶层目录的genie_app/base/genie_event.c中,声明在对应的h文件中:
1 2 3 4 5 6 7 8 void genie_event (E_GENIE_EVENT event, void *args) ;
可以看到这个函数是用于处理事件的,我们看一下都是怎么使用的在 genie_app/bluetooth/mesh/genie_mesh.c中
1 2 3 4 5 6 static void _genie_mesh_ready(int err){ genie_event(GENIE_EVT_SDK_MESH_INIT, NULL ); }
可以看到这个函数传入了一个宏GENIE_EVT_SDK_MESH_INIT,这其实就是一个事件的宏,然后在函数中进行处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 void genie_event (E_GENIE_EVENT event, void *p_arg) { E_GENIE_EVENT next_event = event; uint8_t ignore_user_event = 0 ; #ifdef CONFIG_MESH_MODEL_TRANS if (event != GENIE_EVT_SDK_TRANS_CYCLE) { GENIE_MESH_EVENT_PRINT(event); } #endif switch (event) { case GENIE_EVT_SW_RESET: user_event(GENIE_EVT_SW_RESET, p_arg); ignore_user_event = 1 ; next_event = _genie_event_handle_sw_reset(); break ; case GENIE_EVT_SDK_MESH_INIT: p_arg = (void *)&g_elem_state[0 ]; next_event = _genie_event_handle_mesh_init(); break ; default : break ; } if (!ignore_user_event) user_event(event, p_arg); if (next_event != event) { genie_event(next_event, p_arg); } }
可以看到这里面有大量的case,每一个case里面会执行对应的分支。而且,在最下面,每一个分支执行完毕之后,若是ignore_user_event为0,都还会执行一个user_event()。
1.2 user_event() 我们找一下它的定义,会发现,它定义在我们的demo中(如app/example/bluetooth/light_ctl_demo/light_ctl_demo.c):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 void user_event (E_GENIE_EVENT event, void *p_arg) { E_GENIE_EVENT next_event = event; switch (event) { case GENIE_EVT_SW_RESET: case GENIE_EVT_HW_RESET_START: _led_flash(5 , 1 ); break ; case GENIE_EVT_HW_RESET_DONE: _reset_light_para(); BT_DBG("GENIE_EVT_HW_RESET_DONE\n" ); break ; case GENIE_EVT_SDK_MESH_INIT: #if defined(BOARD_TG7100B) || defined(BOARD_CH6121EVB) _user_init(); #else _led_init(); _init_light_para(); _user_init(); if (!genie_reset_get_flag()) { next_event = GENIE_EVT_SDK_ANALYZE_MSG; } #endif break ; case GENIE_EVT_SDK_MESH_PROV_SUCCESS: _led_flash(3 , 0 ); break ; case GENIE_EVT_SDK_TRANS_CYCLE: case GENIE_EVT_SDK_ACTION_DONE: { elem_state_t *p_elem = (elem_state_t *)p_arg; _led_ctrl(p_elem); if (event == GENIE_EVT_SDK_ACTION_DONE) _save_light_state(p_elem); break ; } case GENIE_EVT_SDK_INDICATE: break ; case GENIE_EVT_SDK_VENDOR_MSG: break ; default : break ; } if (next_event != event) { genie_event(next_event, p_arg); } }
这里也是一堆的case分支,每个分支会去处理不同的事,我们看到有这么一个分支GENIE_EVT_SDK_MESH_INIT,是不是很熟悉,上面的genie_event也有,也就是说,上面调用了genie_event()之后,就会进入user_event()进行处理。
1.3 总结 从上面的分析可知,当出现有genie_event()时候会处理一些内容,然后就会调用user_event()去处理用户自定义事件。
2. LED控制 2.1 light_ctl_default.c文件 这里有坑,这只是第一次写的时候踩的,就放在这里吧。后面直接看2.6节即可。
2.1.1 _led_init() 这个函数在app/example/bluetooth/light_ctl_demo/light_ctl_default.c中定义,我们在这里完成这个初始化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #define LED0_PIN TC825X_GET_PIN_NUM(GPIO_PB4) #define LED1_PIN TC825X_GET_PIN_NUM(GPIO_PB5) #define LED2_PIN TC825X_GET_PIN_NUM(GPIO_PC1) static gpio_dev_t led_dev[3 ] = {0 };static void _led_init(void ){ int32_t ret = 0 ; led_dev[0 ].port = LED0_PIN; led_dev[0 ].config = OUTPUT_PUSH_PULL; ret = hal_gpio_init(&led_dev[0 ]); if (ret != 0 ) { printf ("gpio init error !\n" ); } led_dev[1 ].port = LED1_PIN; led_dev[1 ].config = OUTPUT_PUSH_PULL; ret = hal_gpio_init(&led_dev[1 ]); if (ret != 0 ) { printf ("gpio init error !\n" ); } led_dev[2 ].port = LED2_PIN; led_dev[2 ].config = OUTPUT_PUSH_PULL; ret = hal_gpio_init(&led_dev[2 ]); if (ret != 0 ) { printf ("gpio init error !\n" ); } printf ("++++++++++ led init! ++++++++++\n" ); return ret; }
2.1.2 _led_set() 这个函数在app/example/bluetooth/light_ctl_demo/light_ctl_default.c中定义,我们在这里完成这个led状态设置:
1 2 3 4 5 6 7 8 9 10 11 static void _led_set(uint8_t onoff, uint16_t actual, uint16_t temperature){ if (onoff) { hal_gpio_output_high(&led_dev[0 ]); } else { hal_gpio_output_low(&led_dev[0 ]); } }
2.1.3 完整代码 点击查看详情
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include <stdio.h> #include <aos/aos.h> #include <hal/soc/gpio.h> #include "drivers/8258/gpio_8258.h" #define LED0_PIN TC825X_GET_PIN_NUM(GPIO_PB4) #define LED1_PIN TC825X_GET_PIN_NUM(GPIO_PB5) #define LED2_PIN TC825X_GET_PIN_NUM(GPIO_PC1) static gpio_dev_t led_dev[3 ] = {0 };static void _led_init(void ){ int32_t ret = 0 ; led_dev[0 ].port = LED0_PIN; led_dev[0 ].config = OUTPUT_PUSH_PULL; ret = hal_gpio_init(&led_dev[0 ]); if (ret != 0 ) { printf ("gpio init error !\n" ); } led_dev[1 ].port = LED1_PIN; led_dev[1 ].config = OUTPUT_PUSH_PULL; ret = hal_gpio_init(&led_dev[1 ]); if (ret != 0 ) { printf ("gpio init error !\n" ); } led_dev[2 ].port = LED2_PIN; led_dev[2 ].config = OUTPUT_PUSH_PULL; ret = hal_gpio_init(&led_dev[2 ]); if (ret != 0 ) { printf ("gpio init error !\n" ); } printf ("++++++++++ led init! ++++++++++\n" ); return ret; } static void _led_set(uint8_t onoff, uint16_t actual, uint16_t temperature){ if (onoff) { hal_gpio_output_high(&led_dev[0 ]); } else { hal_gpio_output_low(&led_dev[0 ]); } }
2.2 _init_light_para() 2.2.1 函数定义 这个函数暂时不需要修改,这里需要了解一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 static void _init_light_para(void ){ uint8_t i = 0 ; E_GENIE_FLASH_ERRCODE ret; memset (g_elem_state, 0 , sizeof (g_elem_state)); elem_state_init(MESH_ELEM_STATE_COUNT, g_elem_state); ret = genie_flash_read_userdata(GFI_MESH_POWERUP, (uint8_t *)g_powerup, sizeof (g_powerup)); if (ret == GENIE_FLASH_SUCCESS) { while (i < MESH_ELEM_STATE_COUNT) { #ifdef CONFIG_GENIE_OTA if (g_powerup[0 ].last_onoff == 0 ) { g_elem_state[0 ].powerup.last_onoff = g_powerup[0 ].last_onoff; g_elem_state[0 ].state.onoff[T_TAR] = 0 ; if (g_powerup[0 ].last_actual) { g_elem_state[0 ].state.actual[T_TAR] = g_powerup[0 ].last_actual; g_elem_state[0 ].powerup.last_actual = g_powerup[0 ].last_actual; } if (g_powerup[0 ].last_temp) { g_elem_state[0 ].state.temp[T_TAR] = g_powerup[0 ].last_temp; g_elem_state[0 ].powerup.last_temp = g_powerup[0 ].last_temp; } clear_trans_para(&g_elem_state[0 ]); } else #endif { memcpy (&g_elem_state[0 ].powerup, &g_powerup[0 ], sizeof (model_powerup_t )); if (g_powerup[0 ].last_actual) { g_elem_state[0 ].state.actual[T_TAR] = g_powerup[0 ].last_actual; } if (g_powerup[0 ].last_temp) { g_elem_state[0 ].state.temp[T_TAR] = g_powerup[0 ].last_temp; } if (g_elem_state[0 ].state.onoff[T_TAR] == 1 ) { g_elem_state[0 ].state.trans_start_time = k_uptime_get() + g_elem_state[0 ].state.delay * 5 ; g_elem_state[0 ].state.trans_end_time = g_elem_state[0 ].state.trans_start_time + get_transition_time(g_elem_state[0 ].state.trans); } } g_elem_state[0 ].state.temp[T_CUR] = g_elem_state[0 ].state.temp[T_TAR]; i++; } } }
这里比较重要的是这个g_elem_state数组
2.2.2 g_elem_state[]数组 这个数组的定义为:
1 2 #define MESH_ELEM_COUNT 1 elem_state_t g_elem_state[MESH_ELEM_STATE_COUNT];
它表示的事蓝牙Mesh中“元素”的状态,这个数组中每个元素(与前面的元素含义不同,这里指数组的元素)的数据类型为:
1 2 3 4 5 6 typedef struct { u8_t elem_index; model_state_t state; model_powerup_t powerup; void *user_data; } elem_state_t ;
powerup是用来干嘛的?我们来看一下它的定义:
1 2 3 4 5 6 7 8 9 typedef struct {#ifdef CONFIG_MESH_MODEL_GEN_ONOFF_SRV u8_t last_onoff; #endif #ifdef CONFIG_MESH_MODEL_LIGHTNESS_SRV u16_t last_actual; #endif } model_powerup_t ;
可以看到里面是一些变量,开了宏之后才会使用,那么具体有什么用?假设我们现在使用天猫精灵开了灯,不巧的是,停电了,那么再有电的时候,我们的灯是不是应该保持停电前的状态呢?为了保存掉电之前的状态,就会把相关的内容保存在这里,保存在这个结构体中的数据会被写入到flash中,重新上电的时候会从flash读取对应的数据,恢复原来的状态。所以这里会有一个全局变量数组,它的成员数量与g_elem_state保持一致,每个“元素”都要有这样的一个保存状态的东西:
1 model_powerup_t g_powerup[MESH_ELEM_STATE_COUNT];
2.3 element_models[] app/example/bluetooth/light_ctl_demo/light_ctl_demo.c中还有这样一个模型的数组:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 static struct bt_mesh_model element_models [] = { BT_MESH_MODEL_CFG_SRV(), BT_MESH_MODEL_HEALTH_SRV(), MESH_MODEL_GEN_ONOFF_SRV(&g_elem_state[0 ]), MESH_MODEL_LIGHTNESS_SRV(&g_elem_state[0 ]), MESH_MODEL_CTL_SRV(&g_elem_state[0 ]), #ifndef CONFIG_ALI_SIMPLE_MODLE MESH_MODEL_GEN_LEVEL_SRV(&g_elem_state[0 ]), MESH_MODEL_CTL_SETUP_SRV(&g_elem_state[0 ]), #endif };
前面我们知道,这里的模型起始就是对应功能,其实网页好像也叫模型产品管理 - 生活物联网平台 (aliyun.com)——功能定义 :
在阿里云上的物模型的定义为:物模型是对设备在云端的功能描述,包括设备的属性、服务和事件。物联网平台通过定义一种物的描述语言来描述物模型,称之为 TSL(即 Thing Specification Language),采用 JSON 格式,您可以根据 TSL 组装上报设备的数据。您可以导出完整物模型,用于云端应用开发;您也可以只导出精简物模型,配合设备端 SDK 实现设备开发。
所以说,这里面的开关属性,就是代码中的MESH_MODEL_GEN_ONOFF_SRV()模型。需要注意的是,一个元素可以有多个模型,也就是多个功能,我们只要放在这里就可以啦,后面会有地方调用到的。
2.4 g_element_vendor_models[] 1 2 3 static struct bt_mesh_model g_element_vendor_models [] = { MESH_MODEL_VENDOR_SRV(&g_elem_state[0 ]), };
这个是通用厂商模型,比如我们要定义一些特殊的功能,这些功能在阿里云生活服务平台没有,就需要用到这个来进行自定义啦。
2.5 elements[] 1 2 3 struct bt_mesh_elem elements [] = { BT_MESH_ELEM(0 , element_models, g_element_vendor_models, 0 ), };
这里就是元素啦,这里可以算是元素的各种信息的填充,
2.6 light_ctl_tc825x.c 我们按照2.1小节去修改对应的文件,定义对应的函数,会发现,这里巨坑,这个源文件似乎并没有参与编译(light_ctl_demo.mk中根本没有这个文件),然后一看还有一个light_ctl_tc825x.c文件,里面也有定义了led相关函数,可是最终调用的是哪里的函数?我们看一下light_ctl_demo.c文件的最后面:
会发现这里通过include来包含了c文件,我们搜一下是否有定义BOARD_TC825X这个宏:
会发现是有定义的,所以我们实际使用的是这个文件中的函数,我们修改这个文件如下,这里相对于原文件,我只修改了一部分的内容。
点击查看详情
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 #include "drivers/8258/gpio_8258.h" #include "vendor/common/alios_app_config.h" #ifdef CONIFG_LIGHT_HONGYAN #define WARM_PIN TC825X_GET_PIN_NUM(GPIO_PB0) #define COLD_PIN TC825X_GET_PIN_NUM(GPIO_PB1) #else #define WARM_PIN TC825X_GET_PIN_NUM(PWM_R) #define COLD_PIN TC825X_GET_PIN_NUM(PWM_G) #endif #define LIGHT_FREQ 32000 static pwm_dev_t light_led_c;static pwm_dev_t light_led_w;uint16_t duty_list[] = { #include "duty_list.h" }; #define LED0_PIN TC825X_GET_PIN_NUM(GPIO_PB4) #define LED1_PIN TC825X_GET_PIN_NUM(GPIO_PB5) #define LED2_PIN TC825X_GET_PIN_NUM(GPIO_PC1) static gpio_dev_t led_dev[3 ] = {0 };static void _led_init(void ){ int32_t ret = 0 ; led_dev[0 ].port = LED0_PIN; led_dev[0 ].config = OUTPUT_PUSH_PULL; ret = hal_gpio_init(&led_dev[0 ]); if (ret != 0 ) { printf ("gpio init error !\n" ); } led_dev[1 ].port = LED1_PIN; led_dev[1 ].config = OUTPUT_PUSH_PULL; ret = hal_gpio_init(&led_dev[1 ]); if (ret != 0 ) { printf ("gpio init error !\n" ); } led_dev[2 ].port = LED2_PIN; led_dev[2 ].config = OUTPUT_PUSH_PULL; ret = hal_gpio_init(&led_dev[2 ]); if (ret != 0 ) { printf ("gpio init error !\n" ); } printf ("++++++++++ led init! ++++++++++\n" ); return ret; } static void _get_led_duty(uint8_t *p_duty, uint16_t actual, uint16_t temperature){ uint8_t cold = 0 ; uint8_t warm = 0 ; if (temperature > LIGHT_CTL_TEMP_MAX) { temperature = LIGHT_CTL_TEMP_MAX; } if (temperature < LIGHT_CTL_TEMP_MIN) { temperature = LIGHT_CTL_TEMP_MIN; } cold = (temperature - LIGHT_CTL_TEMP_MIN) * 100 / (LIGHT_CTL_TEMP_MAX - LIGHT_CTL_TEMP_MIN); warm = 100 - cold; p_duty[LED_COLD_CHANNEL] = (actual * cold) / 65500 ; p_duty[LED_WARM_CHANNEL] = (actual * warm) / 65500 ; if (p_duty[LED_COLD_CHANNEL] == 0 && p_duty[LED_WARM_CHANNEL] == 0 ) { if (temperature > (LIGHT_CTL_TEMP_MAX - LIGHT_CTL_TEMP_MIN)>>1 ) { p_duty[LED_COLD_CHANNEL] = 1 ; } else { p_duty[LED_WARM_CHANNEL] = 1 ; } } } static int _set_pwm_duty(uint8_t channel, uint8_t duty){ int err = -1 ; pwm_config_t pwm_cfg; if (duty > 100 ) { LIGHT_DBG("invaild" ); return -1 ; } pwm_cfg.freq = LIGHT_FREQ; pwm_cfg.duty_cycle = (float )duty_list[duty]/duty_list[100 ]; if (channel == LED_COLD_CHANNEL) { err = hal_pwm_para_chg(&light_led_c, pwm_cfg); if (err) { LIGHT_DBG("cold err %d" , err); return -1 ; } } else if (channel == LED_WARM_CHANNEL) { err = hal_pwm_para_chg(&light_led_w, pwm_cfg); if (err) { LIGHT_DBG("warm err %d" , err); return -1 ; } } return 0 ; } static void _led_set(uint8_t onoff, uint16_t actual, uint16_t temperature){ if (onoff) { hal_gpio_output_high(&led_dev[0 ]); } else { hal_gpio_output_low(&led_dev[0 ]); } }
3. 工程代码 详细的修改可以看这里:feat:支持天猫精灵和APP控制LED灯 · sumumm/my-genie-bt-mesh-stack@95e872e (github.com)
4. 编译工程 1 aos make bluetooth.light_ctl_demo@tc825x
5. 烧录程序
6. 实验现象 我们烧录完成后,需要重新用天猫精灵连接开发板,并且会有这些打印信息:
这就说明我们的LED初始化过了。
二、呼吸灯实现 1. 亮度模型? 我们可以到genie_app/bluetooth/mesh/mesh_model/inc目录找一些已经定义好的模型,像亮度模型是直接有的。
2. 代码修改 这里不写了,就是加上pwm就可以了。直接看github仓库本次提交的修改吧:feat:天猫精灵调节灯亮度实验 · sumumm/my-genie-bt-mesh-stack@b4d5978 (github.com)
3. 云端属性添加 我们需要在功能定义中添加一个亮度属性:
需要注意的是,这里实际传到我们使用的地方的时候,值范围是0~65536,而非0~100,另外这里是可以配置的:
4. 编译工程 1 aos make bluetooth.light_ctl_demo@tc825x
5. 烧录程序 跟前面配置一样。
6. 实验现象 首先初始化会有如下打印信息:
我们的led和pwm都已初始化。然后使用天猫精灵连接设备,然后用语音“灯的亮度调到100”来调整灯的亮度。