智能烧水壶 (Bluetooth版)03——离线控制功能实现篇
Posted 三明治开发社区
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了智能烧水壶 (Bluetooth版)03——离线控制功能实现篇相关的知识,希望对你有一定的参考价值。
前言
常见的烧水壶一般有煮沸、保温功能,本案例在此基础上,使用涂鸦BLE SDK和BLE模组实现了对烧水壶的远程控制,包括保温温度设置、水质模式选择、预约烧水等功能,另外还增加了干烧报警等功能,使烧水壶更加智能化。
完整Demo可在 tuya-iotos-embeded-demo-ble-smart-kettle 中获取。
功能设定
本案例中的智能烧水壶设备上提供煮沸、保温2个按键,以及3个指示灯和1个蜂鸣器用于状态提示,可以本地控制也可以远程控制,具体功能设定如下:
功能 | 说明 |
---|---|
煮沸 | 轻触煮沸键,蜂鸣器“滴”一声提醒,切换煮沸功能开/关; 煮沸功能打开时,红灯亮,加热打开直至达到煮沸温度; 煮沸功能关闭或煮沸完成时,红灯灭,加热关闭; |
保温 | 轻触保温键,蜂鸣器“滴”一声提醒,切换保温功能开/关; 保温功能打开且未达到保温温度时,橙灯亮,绿灯灭,加热根据当前温度打开或关闭; 保温功能关闭或达到保温温度时,橙灯灭,绿灯亮,加热关闭; 用水类型为自来水时,先加热至煮沸再保温至保温温度; 用水类型为纯净水时,直接加热至保温温度; 保温温度默认为55℃,可通过APP设置,设置范围在45-90℃; 用水类型默认为自来水,可通过APP设置; |
干烧报警 | 检测到干烧后自动关闭加热,硬件断电,蜂鸣器长鸣报警; |
配网 | 上电配网,3分钟后未配网成功,仅可本地控制; 长按保温键5秒进入配网状态,3分钟后未配网成功,仅可本地控制; 等待配网时,绿灯快闪; |
远程控制 | 可通过APP操作的项目有:打开/关闭煮沸功能和保温功能、设置保温温度和用水类型、查看当前温度和故障状态、预约烧水时间; |
后面我们会一一介绍实现上述功能的具体方案和过程。
离线控制功能实现
(1)指示灯驱动控制
指示灯作为烧水壶状态指示和配网指示,驱动形式为电平驱动,基本设置如下:
指示灯 | 管脚 | H | L | 显示模式 |
---|---|---|---|---|
红色 P_LED_RED | P24/GPIO_PB5 | 灭 | 亮 | 固定(常亮/常灭) |
橙色 P_LED_ORANGE | P26/GPIO_PB4 | 灭 | 亮 | 固定(常亮/常灭) |
绿色 P_GREEN | P6/GPIO_PD2 | 灭 | 亮 | 固定(常亮/常灭) / 闪烁(0.2s亮0.2s灭) |
代码实现:
#define P_LED_RED GPIO_PB5 /* P24 */
#define P_LED_ORANGE GPIO_PB4 /* P26 */
#define P_LED_GREEN GPIO_PD2 /* P6 */
#define LED_TWINKLE_TIME 200 /* 0.2s */
LED_MODE_E g_led_green_mode = LED_MODE_FIX;
LED_MODE_E g_led_green_status = OFF;
/* LED端口初始化 */
void led_init(void)
{
gpio_set_func(P_LED_RED, AS_GPIO);
gpio_set_output_en(P_LED_RED, 1);
gpio_write(P_LED_RED, 1);
gpio_set_func(P_LED_ORANGE, AS_GPIO);
gpio_set_output_en(P_LED_ORANGE, 1);
gpio_write(P_LED_ORANGE, 1);
gpio_set_output_en(P_LED_GREEN, 1);
gpio_set_func(P_LED_GREEN, AS_GPIO);
gpio_write(P_LED_GREEN, 1);
}
/* 红色LED亮灭控制 */
void set_led_red(bool b_on_off)
{
static bool s_last_status = 0;
if (s_last_status != b_on_off) {
if (b_on_off == ON) {
gpio_write(P_LED_RED, 0);
} else {
gpio_write(P_LED_RED, 1);
}
s_last_status = b_on_off;
}
}
/* 橙色LED亮灭控制 */
void set_led_orange(bool b_on_off)
{
static bool s_last_status = 0;
if (s_last_status != b_on_off) {
if (b_on_off == ON) {
gpio_write(P_LED_ORANGE, 0);
} else {
gpio_write(P_LED_ORANGE, 1);
}
s_last_status = b_on_off;
}
}
/* 绿色LED亮灭控制 */
void set_led_green(bool b_on_off)
{
static bool s_last_status = 0;
if (s_last_status != b_on_off) {
if (b_on_off == ON) {
gpio_write(P_LED_GREEN, 0);
} else {
gpio_write(P_LED_GREEN, 1);
}
s_last_status = b_on_off;
}
}
/* 绿色LED模式设置 */
void set_led_green_mode(LED_MODE_E mode)
{
g_led_green_mode = mode;
}
/* 绿色LED状态设置 */
void set_led_green_status(uint8_t status)
{
g_led_green_status = status;
}
/* 绿色LED状态更新 */
void update_led_green_status(void)
{
static bool s_status = 0;
static uint32_t s_twinkle_tm = 0;
switch (g_led_green_mode) {
case LED_MODE_FIX: /* 固定模式:根据状态设置控制LED亮灭 */
set_led_green(g_led_green_status);
s_twinkle_tm = 0;
break;
case LED_MODE_TWINKLE: /* 闪烁模式:0.2s翻转一次亮灭状态 */
if (!clock_time_exceed(s_twinkle_tm, LED_TWINKLE_TIME*1000)) {
break;
}
s_twinkle_tm = clock_time();
s_status = !s_status;
set_led_green(s_status);
break;
default:
break;
}
}
(2)继电器驱动控制
继电器的通断用于控制加热电路,驱动形式为电平驱动,基本设置如下:
继电器 | 管脚 | H | L |
---|---|---|---|
继电器 P_RELAY | P14/GPIO_PD3 | 打开加热 | 关闭加热 |
代码实现:
#define P_RELAY GPIO_PD3 /* P14 */
/* 继电器端口初始化 */
void relay_init(void)
{
gpio_set_func(P_RELAY, AS_GPIO);
gpio_set_output_en(P_RELAY, 1);
gpio_write(P_RELAY, 0);
}
/* 继电器通断控制 */
void set_relay(bool b_on_off)
{
static bool s_last_status = 0;
if (s_last_status != b_on_off) {
if (b_on_off == ON) {
gpio_write(P_RELAY, 1);
} else {
gpio_write(P_RELAY, 0);
}
s_last_status = b_on_off;
}
}
(3)蜂鸣器驱动控制
蜂鸣器作为按键提示音和故障报警提示音,驱动形式为方波信号驱动,基本设置如下:
蜂鸣器 | 管脚 | 模式 | 方波信号(PWM) |
---|---|---|---|
蜂鸣器 P_BUZZER | P17/GPIO_PD4 | 停止 / 滴一声(70ms) / 故障(长鸣) | 通道:PWM2 周期:500us (2KHz) 占空比:50% |
代码实现:
#define P_BUZZER GPIO_PD4 /* P17 */
#define PWM_ID_BUZZER PWM2_ID /* PWM2 */
#define BUZZER_ONCE_TIME 70 /* 70ms */
typedef BYTE_T BUZZER_MODE_E;
#define BUZZER_MODE_STOP 0x00 /* 停止 */
#define BUZZER_MODE_ONCE 0x01 /* 滴一声 */
#define BUZZER_MODE_FAULT 0x02 /* 故障 */
static uint8_t sg_buzzer_timer = OFF;
static uint32_t sg_buzzer_tm = 0;
/* 蜂鸣器端口及PWM配置初始化 */
void buzzer_pwm_init(void)
{
pwm_set_clk(CLOCK_SYS_CLOCK_HZ, CLOCK_SYS_CLOCK_HZ);
gpio_set_func(P_BUZZER, AS_PWM2_N);
pwm_set_mode(PWM_ID_BUZZER, PWM_NORMAL_MODE);
pwm_set_cycle_and_duty(PWM_ID_BUZZER, (uint16_t)(500 * CLOCK_SYS_CLOCK_1US), (uint16_t)(250 * CLOCK_SYS_CLOCK_1US)); /* 500us 2KHz 50% */
gpio_write(P_BUZZER, 0);
}
/* 蜂鸣器开关控制 */
static void set_buzzer(bool b_on_off)
{
static bool s_last_status = 0;
if (s_last_status != b_on_off) {
if (b_on_off == ON) {
pwm_start(PWM_ID_BUZZER);
} else {
pwm_stop(PWM_ID_BUZZER);
gpio_write(P_BUZZER, 0);
}
s_last_status = b_on_off;
}
}
/* 蜂鸣器模式控制 */
void set_buzzer_mode(BUZZER_MODE_E mode)
{
switch (mode) {
case BUZZER_MODE_STOP:
set_buzzer(OFF);
break;
case BUZZER_MODE_ONCE:
set_buzzer(ON);
sg_buzzer_timer = ON;
sg_buzzer_tm = clock_time();
break;
case BUZZER_MODE_FAULT:
set_buzzer(ON);
break;
default:
break;
}
}
/* 蜂鸣器状态更新 */
void update_buzzer_status(void)
{
if (sg_buzzer_timer == ON) {
if (!clock_time_exceed(sg_buzzer_tm, BUZZER_ONCE_TIME*1000)) {
return;
}
set_buzzer(OFF);
sg_buzzer_timer = OFF;
}
}
(4)按键检测与处理
为了后期的程序扩展,采用注册回调函数的方式实现按键检测与处理。
代码实现:
a. 按键注册信息及初始化
用户可注册内容设定如下:
typedef void(* KEY_CALLBACK)();
typedef struct {
uint16_t key1_pin; /* 按键1所用I/O口 */
uint16_t key2_pin; /* 按键2所用I/O口 */
KEY_CALLBACK key1_short_press_cb; /* 按键1短按触发的回调函数 */
KEY_CALLBACK key2_short_press_cb; /* 按键2短按触发的回调函数 */
KEY_CALLBACK key1_long_press_cb; /* 按键1长按触发的回调函数 */
KEY_CALLBACK key2_long_press_cb; /* 按键2长按触发的回调函数 */
uint32_t key1_long_press_time; /* 按键1长按时间设置(ms) */
uint32_t key2_long_press_time; /* 按键2长按时间设置(ms) */
uint32_t scan_time; /* 按键1扫描间隔设置(ms) */
} TS02N_KEY_DEF_T;
接下来定义按键检测所需的状态变量,和用户注册内容共同管理,然后编写按键初始化函数:
/* 按键状态 */
typedef struct {
uint8_t cur_code;
uint8_t prv_code;
uint32_t cur_time;
uint32_t prv_time;
} TS02N_KEY_STATUS_T;
/* 按键管理 */
typedef struct {
TS02N_KEY_DEF_T* ts02n_key_def_s;
TS02N_KEY_STATUS_T ts02n_key_status_s;
} TS02N_KEY_MANAGE_T;
static TS02N_KEY_MANAGE_T *sg_key_mag = NULL;
/* 按键初始化 */
uint8_t ts02n_key_init(TS02N_KEY_DEF_T* key_def)
{
/* 按键信息初始化 */
sg_key_mag = (TS02N_KEY_MANAGE_T *)tuya_ble_malloc(sizeof(TS02N_KEY_MANAGE_T));
memset(sg_key_mag, 0, sizeof(TS02N_KEY_MANAGE_T));
sg_key_mag->ts02n_key_def_s = key_def;
/* 回调函数检查 */
if ((key_def->key1_short_press_cb == NULL) &&
(key_def->key2_short_press_cb == NULL) &&
(key_def->key1_long_press_cb == NULL) &&
(key_def->key2_long_press_cb == NULL)) {
tuya_ble_free((uint8_t *)sg_key_mag);
return KEY_INIT_ERR;
}
/* 端口初始化 */
gpio_set_func(key_def->key1_pin, AS_GPIO);
gpio_set_input_en(key_def->key1_pin, 1);
gpio_setup_up_down_resistor(key_def->key1_pin, PM_PIN_PULLUP_10K);
gpio_set_func(key_def->key2_pin, AS_GPIO);
gpio_set_input_en(key_def->key2_pin, 1);
gpio_setup_up_down_resistor(key_def->key2_pin, PM_PIN_PULLUP_10K);
return KEY_INIT_OK;
}
b. 按键扫描及判断处理
首先分别定义按键1和按键2的键值以及短按确认时间:
#define KEY1_CODE 0x01
#define KEY2_CODE 0x02
#define KEY_PRESS_SHORT_TIME 50
然后编写定时循环的按键状态扫描函数和按键处理函数:
/* 获取当前键值 */
static uint8_t get_key_code(void)
{
uint8_t key_code = 0;
/* 按键1 */
if (gpio_read(sg_key_mag->ts02n_key_def_s->key1_pin) == 0) {
key_code |= KEY1_CODE;
} else {
key_code &= ~KEY1_CODE;
}
/* 按键2 */
if (gpio_read(sg_key_mag->ts02n_key_def_s->key2_pin) == 0) {
key_code |= KEY2_CODE;
} else {
key_code &= ~KEY2_CODE;
}
return key_code;
}
/* 扫描按键状态 */
static void update_key_status(uint32_t time_inc)
{
uint8_t key_code;
key_code = get_key_code();
sg_key_mag->ts02n_key_status_s.prv_time = sg_key_mag->ts02n_key_status_s.cur_time;
sg_key_mag->ts02n_key_status_s.cur_time += time_inc;
if (key_code != sg_key_mag->ts02n_key_status_s.cur_code) {
sg_key_mag->ts02n_key_status_s.prv_code = sg_key_mag->ts02n_key_status_s.cur_code;
sg_key_mag->ts02n_key_status_s.cur_code = key_code;
sg_key_mag->ts02n_key_status_s.prv_time = sg_key_mag->ts02n_key_status_s.cur_time;
sg_key_mag->ts02n_key_status_s.cur_time = 0;
} else {
sg_key_mag->ts02n_key_status_s.prv_code = sg_key_mag->ts02n_key_status_s.cur_code;
}
}
/* 判断[key_code]按键按压时间是否超过[press_time] */
static uint8_t is_key_press_over_time(uint8_t key_code, uint32_t press_time)
{
if (sg_key_mag->ts02n_key_status_s.cur_code == key_code) {
if ((sg_key_mag->ts02n_key_status_s.cur_time >= press_time) &&
(sg_key_mag->ts02n_key_status_s.prv_time < press_time)) {
return 1;
}
}
return 0;
}
/* 判断[key_code]按键释放时按压时间是否少于[press_time] */
static uint8_t is_key_release_to_release_less_time(uint8_t key_code, uint32_t press_time)
{
if ((sg_key_mag->ts02n_key_status_s.prv_code == key_code) &&
(sg_key_mag->ts02n_key_status_s.cur_code != key_code)) {
if ((sg_key_mag->ts02n_key_status_s.prv_time >= KEY_PRESS_SHORT_TIME) &&
(sg_key_mag->ts02n_key_status_s.prv_time < press_time)) {
return 1;
}
}
return 0;
}
/* 检测与处理按键事件 */
static void detect_and_handle_key_event(void)
{
/* 按键1处理 */
if (sg_key_mag->ts02n_key_def_s->key1_long_press_cb != NULL) {
/* 长按 */
if (is_key_press_over_time(KEY1_CODE, sg_key_mag->ts02n_key_def_s->key1_long_press_time)) {
sg_key_mag->ts02n_key_def_s->key1_long_press_cb();
TUYA_APP_LOG_DEBUG("key1 is long pressed");
}
/* 短按 */
if (sg_key_mag->ts02n_key_def_s->key1_short_press_cb != NULL) {
if (is_key_release_to_release_less_time(KEY1_CODE, sg_key_mag->ts02n_key_def_s->key1_long_press_time)) {
sg_key_mag->ts02n_key_def_s->key1_short_press_cb();
TUYA_APP_LOG_DEBUG("key1 is pressed");
}
}
} else {
/* 短按 */
if (sg_key_mag->ts02n_key_def_s->key1_short_press_cb != NULL) {
if (is_key_press_over_time(KEY1_CODE, KEY_PRESS_SHORT_TIME)) {
sg_key_mag->ts02n_key_def_s->key1_short_press_cb();
TUYA_APP_LOG_DEBUG("key1 is pressed");
}
}
}
/* 按键2处理 */
if (sg_key_mag->ts02n_key_def_s->key2_long_press_cb != NULL) {
/* 长按 */
if (is_key_press_over_time(KEY2_CODE, sg_key_mag->ts02n_key_def_s->key2_long_press_time)) {
sg_key_mag->ts02n_key_def_s->key2_long_press_cb();
TUYA_APP_LOG_DEBUG("key2 is long pressed");
}
/* 短按 */
if (sg_key_mag->ts02n_key_def_s->key2_short_press_cb != NULL) {
if (is_key_release_to_release_less_time(KEY2_CODE, sg_key_mag->ts02n_key_def_s->key2_long_press_time)) {
sg_key_mag->ts02n_key_def_s->key2_short_press_cb();
TUYA_APP_LOG_DEBUG("key2 is pressed");
}
}
} else {
/* 短按 */
if (sg_key_mag->ts02n_key_def_s->key2_short_press_cb != NULL) {
if (is_key_press_over_time(KEY2_CODE, KEY_PRESS_SHORT_TIME)) {
sg_key_mag->ts02n_key_def_s->key2_short_press_cb();
TUYA_APP_LOG_DEBUG("key2 is pressed");
}
}
}
}
/* 按键定时循环函数 */
void ts02n_key_loop(void)
{
static uint32_t s_key_scan_tm = 0;
/* 定时时间判断 */
if (!clock_time_exceed(s_key_scan_tm, (sg_key_mag->ts02n_key_def_s->scan_time)*1000)) {
return;
}
s_key_scan_tm = clock_time(); /* 记录当前时间 */
update_key_status(sg_key_mag->ts02n_key_def_s->scan_time); /* 扫描按键状态 */
detect_and_handle_key_event(); /* 判断与处理 */
}
(5)温度采集与处理
根据硬件方案中关于NTC温度传感器的介绍可知,通过ADC模块采集到的端口电压值即可换算出当前温度值。由于本案例使用的芯片平台不支持浮点数运算,这里我们采用查表法来获取当前温度。首先我们编写一个脚本,将NTC的R-T表中的电阻值读出并转换为电压值,然后按照顺序放入数组中存储,这样我们就可以通过查询数组将电压值转换为温度值。脚本可在tuya-iotos-embeded-demo-ble-temperature-alarm中获取。
a. 温度采集
NTC端口设置如下:
温度传感器 | 管脚 |
---|---|
温度传感器 P_NTC | ADC/GPIO_PB6 |
代码实现:
#define P_NTC GPIO_PB6 /* ADC */
#define TEMP_ARRAY_MIN_VALUE 0 /* 数组中第一个数据对应的温度 */
#define TEMP_ARRAY_SIZE 120 /* 数据个数 */
/* NTC(B3950/100K) 温度-电压对应表 */
const uint16_t vol_data_of_temp[TEMP_ARRAY_SIZE] = {
190, 199, 209, 219, 229, 240, 251, 263, 275, 288, 301, 314, 328, 342, 357, 372, 388, 404, 420, 437, /* 0 ~ 19 */
455, 473, 491, 510, 530, 549, 570, 591, 612, 634, 656, 679, 702, 725, 749, 774, 799, 824, 849, 875, /* 20 ~ 39 */
902, 928, 955, 982, 1010, 1038, 1066, 1094, 1123, 1152, 1181, 1210, 1239, 1268, 1298, 1327, 1357, 1386, 1416, 1446, /* 40 ~ 59 */
1475, 1505, 1535, 1564, 1593, 1623, 1652, 1681, 1710, 1738, 1767, 1795, 1823, 1851, 1878, 1906, 1933, 1959, 1986, 2012, /* 60 ~ 79 */
2038, 2063, 2088, 2113, 2138, 2162, 2185, 2209, 2232, 2255, 2277, 2299, 2320, 2342, 2362, 2383, 2403, 2423, 2442, 2461, /* 80 ~ 99 */
2480, 2498, 2516, 2534, 2551, 2568, 2584, 2600, 2616, 2632, 2647, 2662, 2676, 2690, 2704, 2718, 2731, 2744, 2757, 2769 /* 100 ~ 119 */
};
/* NTC端口及ADC模块初始化 */
void ntc_adc_init(void)
{
adc_init();
adc_base_init(P_NTC);
adc_power_on_sar_adc(1);
}
/* 判断电压值是否在data[num1]和data[num2]之间 */
static uint8_t is_vol_value_between(uint16_t value, uint8_t num1, uint8_t num2)
{
if ((value >= vol_data_of_temp[num1]) && (value <= vol_data_of_temp[num2])) {
return 1;
} else {
return 0;
}
}
/* 判断电压值更接近data[num1]还是data[num2] */
static uint8_t get_closer_num(uint16_t value, uint8_t num1, uint8_t num2)
{
if ((value - vol_data_of_temp[num1]) < (vol_data_of_temp[num2] - value)) {
return num1;
} else {
return num2;
}
}
/* 转换电压值为温度值 */
static uint8_t transform_vol_to_temp(uint16_t vol_value)
{
uint8_t comp_num;
uint8_t min = 0;
uint8_t max = TEMP_ARRAY_SIZE - 1;
uint8_t temp = 0;
if (vol_value <= vol_data_of_temp[min]) {
return TEMP_ARRAY_MIN_VALUE;
}
if (vol_value >= vol_data_of_temp[max]) {
return (TEMP_ARRAY_MIN_VALUE + TEMP_ARRAY_SIZE - 1);
}
while (1) {
comp_num = (max + min) / 2;
if (vol_value == vol_data_of_temp[comp_num]) {
temp = comp_num + TEMP_ARRAY_MIN_VALUE;
break;
} else if (vol_value < vol_data_of_temp[comp_num]) {
if (is_vol_value_between(vol_value, comp_num-1, comp_num)) {
temp = get_closer_num(vol_value, comp_num-1, comp_num) + TEMP_ARRAY_MIN_VALUE;
break;
} else {
max = comp_num;
}
} else {
if (is_vol_value_between(vol_value, comp_num, comp_num+1)) {
temp = get_closer_num(vol_value, comp_num, comp_num+1) + TEMP_ARRAY_MIN_VALUE;
break;
} else {
min = comp_num;
}
}
}
return temp;
}
/* 获取当前温度 */
uint8_t get_cur_temp(void)
{
uint8_t ntc_temp;
uint16_t ntc_vol_value;
/* NTC端口及ADC模块初始化(需要在每次读取前初始化) */
ntc_adc_init();
/* 读取A/D转换结果(这里得到的直接是单位为mV的电压值) */
ntc_vol_value = (uint16_t)adc_sample_and_get_result();
TUYA_APP_LOG_DEBUG("voltage: %d", ntc_vol_value);
/* 将电压值转换为温度值 */
ntc_temp = transform_vol_to_temp(ntc_vol_value);
TUYA_APP_LOG_DEBUG("temperature: %d", ntc_temp);
return ntc_temp;
}
(6)应用层功能实现
在以上相关驱动代码实现后,下面进行智能烧水壶相关功能的实现。
a. 初始化函数与主循环函数的编写
/* 初始化函数 【在tuya_ble_app_demo.c的tuya_ble_app_init()函数中调用】 */
void tuya_app_kettle_init(void)
{
memset(&g_kettle, 0, sizeof(g_kettle)); /* 变量初始化 */
memset(&g_kettle_flag, 0, sizeof(g_kettle_flag)); /* 标志初始化 */
set_keep_warm_temp(TEMP_KEEP_WARM_DEFAULT); /* 设置默认保温温度 */
led_init(); /* 指示灯端口初始化 */
relay_init(); /* 继电器端口初始化 */
buzzer_pwm_init(); /* 蜂鸣器端口及PWM模块初始化 */
ntc_adc_init(); /* 温度传感器端口及ADC模块初始化 */
ts02n_key_init(&user_ts02n_key_def_s); /* 按键注册并初始化 */
ble_connect_status_init(); /* 蓝牙连接状态初始化(在下一节介绍) */
}
/* 主循环函数 【在tuya_ble_app_demo.c的app_exe()函数中调用】 */
void tuya_app_kettle_loop(void)
{
update_ble_status(); /* 蓝牙连接状态更新(在下一节介绍) */
update_cur_temp(); /* 温度更新处理 */
ts02n_key_loop(); /* 按键循环处理 */
update_kettle_mode(); /* 模式更新处理 */
update_led_green_status(); /* 指示灯定时处理 */
update_buzzer_status(); /* 蜂鸣器定时处理 */
}
b. 温度更新与故障检测
每2秒更新一次温度值,如果发生温度变化,则将数据更新上报至云端,并进行一次故障检测;如果故障状态变化,更新故障信息至云端:
#define TEMP_UPPER_LIMIT 105 /* 报警温度阈值 */
#define TIME_GET_TEMP 2000 /* 温度更新间隔:2s */
typedef BYTE_T FAULT_E; /* 故障 */
#define FAULT_NORMAL 0x00 /* 正常 */
#define FAULT_LACK_WATER 0x01 /* 缺水 */
/* 更新故障信息 */
static void update_fault(FAULT_E fault)
{
g_kettle.fault = fault;
report_one_dp_data(DP_ID_FAULT, g_kettle.fault);
TUYA_APP_LOG_DEBUG("fault: %d", g_kettle.fault);
}
/* 烧水壶停止工作 */
static void stop_kettle(void)
{
set_boil_turn(OFF);
set_keep_warm_turn(OFF);
set_led_red(OFF);
set_led_orange(OFF);
set_led_green_status(OFF);
set_relay(OFF);
}
/* 故障检测处理 */
static void detect_and_handle_fault_event(void)
{
if (g_kettle.fault == FAULT_NORMAL) {
if (g_kettle.temp_cur >= TEMP_UPPER_LIMIT) { /* 超过温度上限时 */
update_fault(FAULT_LACK_WATER); /* 更新故障状态为缺水干烧 */
set_buzzer_mode(BUZZER_MODE_FAULT); /* 蜂鸣器长鸣 */
stop_kettle(); /* 停止工作 */
}
} else {
if (g_kettle.temp_cur < TEMP_UPPER_LIMIT) { /* 温度恢复后 */
update_fault(FAULT_NORMAL); /* 更新故障状态为无故障 */
set_buzzer_mode(BUZZER_MODE_STOP); /* 蜂鸣器停止 */
set_work_mode(MODE_NATURE); /* 切换至自然模式 */
}
}
}
/* 更新当前温度 */
static void update_cur_temp(void)
{
uint8_t temp;
static uint32_t s_get_temp_tm = 0;
/* 2秒定时 */
if (!clock_time_exceed(s_get_temp_tm, TIME_GET_TEMP*1000)) {
return;
}
s_get_temp_tm = clock_time();
/* 获取当前温度 */
temp = get_cur_temp();
if (g_kettle.temp_cur != temp) { /* 温度变化? */
g_kettle.temp_cur = temp; /* 更新当前温度 */
report_one_dp_data(DP_ID_TEMP_CUR, g_kettle.temp_cur);
detect_and_handle_fault_event(); /* 故障检测处理 */
}
}
c. 按键注册与事件响应处理
按键管脚及相关操作的主要响应内容设置如下:
按键 | 管脚 | 操作 | 响应 |
---|---|---|---|
煮沸键 P_KEY_BOIL | P7/GPIO_PC3 | 轻触 | 打开/关闭煮沸功能 |
保温键 P_KEY_KEEP | P8/GPIO_PC2 | 轻触 长按5秒 | 打开/关闭保温功能 进入配网状态 |
按键信息注册如下:
#define P_KEY_BOIL GPIO_PC3
#define P_KEY_KEEP GPIO_PC2
/* 用户按键信息注册 */
TS02N_KEY_DEF_T user_ts02n_key_def_s = {
.key1_pin = P_KEY_BOIL, /* P7 */
.key2_pin = P_KEY_KEEP, /* P8 */
.key1_short_press_cb = key_boil_short_press_cb_fun, /* 煮沸键轻触处理 */
.key2_short_press_cb = key_keep_short_press_cb_fun, /* 保温键轻触处理 */
.key1_long_press_cb = NULL, /* 无煮沸键长按功能 */
.key2_long_press_cb = key_keep_long_press_cb_fun, /* 保温键长按5秒处理 */
.key1_long_press_time = 0, /* 无煮沸键长按功能 */
.key2_long_press_time = 5000, /* 5s */
.scan_time = 10, /* 10ms */
};
/* 切换煮沸开/关状态 */
static void switch_boil_turn(void)
{
if (g_kettle.boil_turn == ON) {
set_boil_turn(OFF); /* 关闭煮沸功能(具体执行内容在[云端控制(3)]中介绍) */
} else {
set_boil_turn(ON); /* 打开煮沸功能 */
}
}
/* 切换保温开/关状态 */
static void switch_keep_warm_turn(void)
{
if (g_kettle.keep_warm_turn == ON) {
set_keep_warm_turn(OFF);/* 关闭保温功能(具体执行内容在[云端控制(3)]中介绍) */
} else {
set_keep_warm_turn(ON); /* 打开保温功能 */
}
}
/* 煮沸键轻触处理 */
void key_boil_short_press_cb_fun(void)
{
if (g_kettle.fault != FAULT_NORMAL) { /* 故障发生时按键无效 */
return;
}
switch_boil_turn(); /* 切换煮沸开/关状态 */
set_buzzer_mode(BUZZER_MODE_ONCE); /* 设置蜂鸣器模式为“滴一声” */
}
/* 保温键轻触处理 */
void key_keep_short_press_cb_fun(void)
{
if (g_kettle.fault != FAULT_NORMAL) { /* 故障发生时按键无效 */
return;
}
switch_keep_warm_turn(); /* 切换保温开/关状态 */
set_buzzer_mode(BUZZER_MODE_ONCE); /* 设置蜂鸣器模式为“滴一声” */
}
/* 保温键长按5秒处理 */
void key_keep_long_press_cb_fun(void)
{
if (F_BLE_BONDING == SET) { /* 已被用户绑定时无效 */
return;
}
try_to_connect_ble(); /* 尝试配网(具体执行内容在[云端控制(1)]中介绍) */
set_buzzer_mode(BUZZER_MODE_ONCE); /* 设置蜂鸣器模式为“滴一声” */
}
d. 模式更新与处理
根据功能设定,我们将烧水壶的工作模式拆分为以下4个模式:
模式 | 条件 | 状态 |
---|---|---|
自然模式 | 煮沸、保温功能均关闭 | 指示灯、加热均关闭 |
煮沸模式 | 煮沸功能打开 | 红灯亮、橙灯灭、绿灯灭、加热打开 |
保温模式1 | 煮沸功能关闭,保温功能打开,自来水模式 | 红灯灭、橙灯亮、绿灯灭、加热打开 |
保温模式2 | 煮沸功能关闭,保温功能打开,纯净水模式 | 红灯灭、橙灯/绿灯/加热根据当前温度设定状态 |
代码实现:
/* 工作模式 */
typedef BYTE_T MODE_E;
#define MODE_NATURE 0x00
#define MODE_BOIL 0x01
#define MODE_KEEP_WARM1 0x02
#define MODE_KEEP_WARM2 0x03
/* 用水类型 */
typedef BYTE_T WATER_TYPE_E;
#define WATER_TYPE_TAP 0x00
#define WATER_TYPE_PURE 0x01
/* 温度相关 */
#define TEMP_BOILED 97 /* 煮沸温度 */
#define TEMP_KEEP_WARM_DEFAULT 55 /* 默认保温温度 */
/* 自然模式 */
static void kettle_mode_nature(void)
{
set_led_red(OFF);
set_led_orange(OFF);
set_led_green_status(OFF);
set_relay(OFF);
}
/* 煮沸模式 */
static void kettle_mode_boil(void)
{
if (g_kettle.temp_cur >= TEMP_BOILED) { /* 达到煮沸温度时 */
set_water_type(WATER_TYPE_PURE); /* 更新用水类型为纯净水 */
set_boil_turn(OFF); /* 关闭煮沸功能 */
set_relay(OFF); /* 关闭加热 */
} else {
set_relay(ON); /* 打开加热 */
}
}
/* 保温模式1 */
static void kettle_mode_keep_warm1(void)
{
if (g_kettle.temp_cur >= TEMP_BOILED) { /* 达到煮沸温度时 */
set_water_type(WATER_TYPE_PURE); /* 更新用水类型为纯净水 */
set_relay(OFF); /* 关闭加热 */
} else {
set_relay(ON); /* 打开加热 */
}
}
/* 保温模式2 */
static void kettle_mode_keep_warm2(void)
{
/* 未达到保温温度时,橙灯亮,绿灯灭,温度高时加热关闭,温度低时加热打开 */
if (g_kettle.temp_cur > g_kettle.temp_set) {
set_led_orange(ON);
set_led_green_status(OFF);
set_relay(OFF);
} else if (g_kettle.temp_cur < (g_kettle.temp_set - 3)) {
set_led_orange(ON);
set_led_green_status(OFF);
set_relay(ON);
/* 达到保温温度时,橙灯灭,绿灯亮,加热关闭 */
} else {
set_led_orange(OFF);
set_led_green_status(ON);
set_relay(OFF);
}
}
/* 运行各模式 */
static void run_kettle(void)
{
if (g_kettle.fault != FAULT_NORMAL) {
return;
}
switch (g_kettle.mode) {
case MODE_NATURE:
kettle_mode_nature();
break;
case MODE_BOIL:
kettle_mode_boil();
break;
case MODE_KEEP_WARM1:
kettle_mode_keep_warm1();
break;
case MODE_KEEP_WARM2:
kettle_mode_keep_warm2();
break;
default:
break;
}
}
/* 模式更新 */
static void update_kettle_mode(void)
{
if (g_kettle.boil_turn == ON) { /* 煮沸功能打开时 */
set_work_mode(MODE_BOIL); /* 进入煮沸模式 */
} else if (g_kettle.keep_warm_turn == ON){ /* 煮沸功能关闭、保温功能打开时 */
if (g_kettle.water_type == WATER_TYPE_TAP) {/* 使用自来水时 */
set_work_mode(MODE_KEEP_WARM1); /* 进入保温模式1(先煮沸再保温) */
} else { /* 使用纯净水时 */
set_work_mode(MODE_KEEP_WARM2); /* 进入保温模式2(直接保温) */
}
} else { /* 煮沸、保温功能都关闭开时 */
set_work_mode(MODE_NATURE); /* 进入自然模式 */
}
run_kettle();
}
以上就是蓝牙版智能烧水壶离线控制功能实现操作方法,如果大家有疑问或者有更好的方案欢迎留言讨论~
以上是关于智能烧水壶 (Bluetooth版)03——离线控制功能实现篇的主要内容,如果未能解决你的问题,请参考以下文章