涂鸦智能暖风机软件实现之LED驱动和断电记忆功能

Posted 三明治开发社区

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了涂鸦智能暖风机软件实现之LED驱动和断电记忆功能相关的知识,希望对你有一定的参考价值。


前言

前面我们已经实现了暖风机的离线按键控制,本文将实现暖风机的LED驱动和断电记忆功能。


一、暖风机的LED灯带显示功能

  • 此次改装的智能暖风机拥有灯光效果,用户可以自行设置氛围灯的开启关闭以及更改颜色;同时配网模式时也会采用灯光渐变的效果作为提示。
功能说明
灯光触摸按键:1个
按键控制,app控制
4种照明模式:
1 rgb1
2 rgb2
3 rgb3
4 rgb4

1.LED作为氛围灯时方案设定

  • 硬件原理图:
    在这里插入图片描述
    采用RGB灯带,根据三路PWM的输出调节灯带的显示颜色,暖风机灯光效果提供四种颜色。

  • 嵌入式实现方案
    使用三个PWM输出引脚,配置输出的模式以及输出占空比。
    PWM引脚设置:

GIN		|	P8
RIN		|	P7
BIN		|	P6
  • 软件实现
    在已有的开发基础上,增加led_color_set.c文件以及头文件,主要内容为引脚的PWM初始化,设置LED不同工作模式的函数代码。

目前整个工程的文件结构如下:

├── src	
|    ├── tuya_drive
|    |  	└── b3950
|    |    		└── b3950.c           //温度传感器驱动相关
|    |  	└── tm1650
|    |    		└──  tm1650.c           
|    |  	└── rgb_led			//LED控制相关
|    |    		└──  led_color_set.c         
|    |  	└── tc309					//按键驱动相关
|    |    		└── tc309.c    
|    |  	└── interrupt				//外部中断
|    |    		└── interrupt.c    
|    |  	└── gpio_control		//gpio控制相关
|    |    		└── gpio_control.c    
|    |  	└── timer
|    |    		└── timer.c           //定时器相关
|    ├── tuya_device.c             //应用层入口文件
|    ├── tuya_thread.c             //主要线程处理文件
|    └── tuya_dp_process.c            //dp数据触发的主要应用文件
|
├── include				//头文件目录
|    ├── tuya_drive_h
|    |  	└── b3950_h
|    |    		└── b3950.h        
|    |  	└── tm1650_h
|    |    		└──  tm1650.h        
|    |  	└── rgb_led_h			//LED控制相关
|    |    		└──  led_color_set.h           
|    |  	└── tc309_h					
|    |    		└── tc309.h    
|    |  	└── interrupt_h				
|    |    		└── interrupt.h    
|    |  	└── gpio_control_h
|    |    		└── gpio_control.h        
|    |  	└── timer_h
|    |    		└── timer.h           
|    ├── tuya_device.h           
|    ├── tuya_thread.h       
|    └── tuya_dp_process.h       
|
└── output              //编译产物

led_color_set.c代码及说明如下:

#include "led_color_set.h"

#define LED_CLOSE 0
#define COLOR1 1
#define COLOR2 2
#define COLOR3 3
#define COLOR4 4
#define STAT_UNPROVISION_LED 5
#define STAT_AP_STA_UNCFG_LED 6
#define STAT_AP_STA_DISC_LED 7
static tuya_pwm_t *rgb_r_pwm = NULL;
static tuya_pwm_t *rgb_g_pwm = NULL;
static tuya_pwm_t *rgb_b_pwm = NULL;
tuya_pwm_t *buzzer_pwm = NULL;
uint8_t rgb_r_pwm_pin = TY_GPIOA_7;
uint8_t rgb_g_pwm_pin = TY_GPIOA_8;
uint8_t rgb_b_pwm_pin = TY_GPIOA_6;
void led_init()
{
    /*color Red init*/
    rgb_r_pwm = (tuya_pwm_t *)tuya_driver_find(TUYA_DRV_PWM, TUYA_PWM0);
    TUYA_PWM_CFG(rgb_r_pwm, rgb_r_pwm_pin, 10 * 1000, 0);
    tuya_pwm_init(rgb_r_pwm);
    tuya_pwm_start(rgb_r_pwm);

    /*color Green init*/
    rgb_g_pwm = (tuya_pwm_t *)tuya_driver_find(TUYA_DRV_PWM, TUYA_PWM1);
    TUYA_PWM_CFG(rgb_g_pwm, rgb_g_pwm_pin, 10 * 1000, 0);
    tuya_pwm_init(rgb_g_pwm);
    tuya_pwm_start(rgb_g_pwm);

    /*color Bule init*/
    rgb_b_pwm = (tuya_pwm_t *)tuya_driver_find(TUYA_DRV_PWM, TUYA_PWM5);
    TUYA_PWM_CFG(rgb_b_pwm, rgb_b_pwm_pin, 10 * 1000, 0);
    tuya_pwm_init(rgb_b_pwm);
    tuya_pwm_start(rgb_b_pwm);
    
}

VOID color_handle(IN int mode)
{
    switch (mode){

    case LED_CLOSE: 
    {
        led_close();
    }
    break;
    case COLOR1:
    {
        color1_set();
    }
    break;
    case COLOR2:
    {
        color2_set();
    }
    break;
    case COLOR3:
    {
        color3_set();
    }
    break;
    case COLOR4:
    {
        color4_set();
    }
    break;
    case STAT_UNPROVISION_LED:
    {
    //配网模式指示灯
        stat_unprovision_led();
    }
    break;
    case STAT_AP_STA_UNCFG_LED:
    {
        //配网模式指示灯
        stat_unprovision_led();
    }
    break;
    case STAT_AP_STA_DISC_LED:
    {
        //配网模式指示灯
        stat_unprovision_led();
    }
    break;
    default:
    break;
    }

}

void color1_set()
{
    tuya_pwm_duty_set(rgb_r_pwm, 1.0);
    tuya_pwm_duty_set(rgb_g_pwm, 0);
    tuya_pwm_duty_set(rgb_b_pwm, 0);

}

void color2_set()
{
    tuya_pwm_duty_set(rgb_r_pwm, 0);
    tuya_pwm_duty_set(rgb_g_pwm, 1.0);
    tuya_pwm_duty_set(rgb_b_pwm, 0);

}

void color3_set()
{
    tuya_pwm_duty_set(rgb_r_pwm, 0);
    tuya_pwm_duty_set(rgb_g_pwm, 0);
    tuya_pwm_duty_set(rgb_b_pwm, 1.0);

}

void color4_set()
{
    tuya_pwm_duty_set(rgb_r_pwm, 0.15);
    tuya_pwm_duty_set(rgb_g_pwm, 0.8);
    tuya_pwm_duty_set(rgb_b_pwm, 0.8);

}
void led_close()
{
    tuya_pwm_duty_set(rgb_r_pwm, 0);
    tuya_pwm_duty_set(rgb_g_pwm, 0);
    tuya_pwm_duty_set(rgb_b_pwm, 0);

}
void stat_unprovision_led()
{
    static float pwm_duty = 0;
    tuya_pwm_duty_set(rgb_r_pwm, pwm_duty);
    tuya_pwm_duty_set(rgb_g_pwm, 0);
    tuya_pwm_duty_set(rgb_b_pwm, 0);
    pwm_duty = pwm_duty + 0.1;
    if(pwm_duty > 1)
    {
        pwm_duty = 0;
    }
}
void stat_ap_sta_uncfg_led()
{
    static float pwm_duty = 0;
    tuya_pwm_duty_set(rgb_r_pwm, 0);
    tuya_pwm_duty_set(rgb_g_pwm, 0);
    tuya_pwm_duty_set(rgb_b_pwm, pwm_duty);
    pwm_duty = pwm_duty + 0.1;
    if(pwm_duty > 1)
    {
        pwm_duty = 0;
    }
}
void stat_ap_sta_disc_led()
{
    static float pwm_duty = 0;
    tuya_pwm_duty_set(rgb_r_pwm, 0);
    tuya_pwm_duty_set(rgb_g_pwm, pwm_duty);
    tuya_pwm_duty_set(rgb_b_pwm, 0);
    pwm_duty = pwm_duty + 0.1;
    if(pwm_duty > 1)
    {
        pwm_duty = 0;
    }
}

上面是LED的驱动程序,led_init()函数放到calorifier_init()中初始化,之后按键控制或者APP控制就可以使用color_handle(IN int mode)来设置LED灯带工作的模式。

2.配网模式下LED状态显示

  • 前面以及实现了LED的驱动,在正常情况用户可以自行切换灯光颜色和选择关闭灯光;配网模式下LED将渐变闪烁提示用户配网,配网成功后灯光将关闭。
  • 什么是配网?
    在刚拿到设备时,打开暖风机长按led调节按键将进入配网模式,此时手机连接WIFI,打开涂鸦智能app点击添加设备->搜索设备,此时就可以搜到暖风机这个设备,下一步按照提示输入WIFI名和密码即可进行连接;连接成功即手机和暖风机配网成功。

配网时LED进行红光渐变闪烁,若三分钟未连接上设备将退出配网模式,LED显示关闭,若连接成功也将关闭LED显示。

  • 配网功能实现
    长按led调节按键时,启动配网程序:
        case KEY4:
        {
            //Short press:led mode key  long press: Reconfigure the network mode
            if(dp_memory_s.switch_bool)
            {
                timer1_init();
                static uint32_t count = 0;
                while(tuya_gpio_read(9) == 0){
                    count++;
                    if(count > 700000){
                        break;
                    }
                }
                if(count > 700000){
                    count = 0;
                    PR_DEBUG("wf_unc");
                    tuya_iot_wf_gw_unactive();
                }else{
                    PR_DEBUG("key_value4");
                    static uint8_t cur_ledmode = 0;
                    cur_ledmode = dp_memory_s.led_mode;
                    cur_ledmode = (cur_ledmode + 1 < 5) ? (cur_ledmode + 1) : 0;
                    led_handle(cur_ledmode);
                }
                PR_DEBUG("led mode:%d",dp_memory_s.led_mode);
            }            

        }

调用tuya_iot_wf_gw_unactive()进入配网,此时系统将调用tuya_device.c文件中的wf_nw_status_cb(IN CONST GW_WIFI_NW_STAT_E stat) 函数从而调用wifi_state_led_reminder(stat)函数。

更改原有的函数内容
STATIC VOID wifi_state_led_reminder(IN CONST GW_WIFI_NW_STAT_E cur_stat)
{
    wf_nw_status_temp = cur_stat;
    PR_NOTICE("wf_nw_status_temp:%d",wf_nw_status_temp);
    
}
此时idle_task线程将根据配网的状态进行led的状态设定
void idle_task(void)
{

    static uint8_t wf_nw_led = 1;
    while(1)
    {
//离线模式倒计时关闭相关代码
    shutdown_time_s.value = ((dev_key_s.temp_time_count > timercount) && (0 < (dev_key_s.temp_time_count - timercount)/6 < 720)) ? (dev_key_s.temp_time_count - timercount)/6 : 0;
    dev_key_s.timer_hour = shutdown_time_s.value/60;
    if((timercount > dev_key_s.temp_time_count) && dev_key_s.temp_time_count && dev_key_s.key_notice){
        PR_DEBUG("SHUTDOWN TIME");
        time_off_handle(1);
        dev_key_s.temp_time_count = 0;
        dev_key_s.timer_hour = 0;
        dev_key_s.key_notice = 0;
        tuya_hal_system_sleep(1000);
    }
    if((dp_memory_s.temperature_value > dp_memory_s.set_temper_value) && dp_memory_s.relay_mode){
        relay_handle(0);
    }
    //配网模式LED灯光的相关状态配置
    if(wf_nw_led && dp_memory_s.switch_bool){
        switch (wf_nw_status_temp)
        {
            case STAT_LOW_POWER:    //WiFi connection network timeout, enter low power mode
            {
                color_handle(0);
                wf_nw_led = 0;
            }
            break;

            case STAT_UNPROVISION: //SamrtConfig connected network mode, waiting for connection 
            {
                color_handle(5);
            }

            break;

            case STAT_AP_STA_UNCFG: //ap connected network mode, waiting for connection 
            {
                color_handle(6);
            }
            break;

            case STAT_AP_STA_DISC:
            case STAT_STA_DISC:     //SamrtConfig/ap connecting...
            {
                color_handle(7);
            }
            break;

            case STAT_CLOUD_CONN:
            case STAT_AP_CLOUD_CONN: //Already connected to Tuya Cloud 
            {
                color_handle(0);
                wf_nw_led = 0;
            }
            break;

            default:
            break;
        }
        tuya_hal_system_sleep(200);
    }
    }
}

  • 上面我们已经实现了配网时LED的提示功能

二、智能暖风机断电记忆功能

1.断电记忆功能

智能暖风机提供的记忆功能如下

功能说明
待机记忆按键,app,主动操作开关键关机为待机状态。
再开启后恢复上一次设置:
温度设置:上一次设置
温度显示:当前环境温度
灯光模式:上一次设置
设备定时关:默认关闭
app定时关:默认关闭。
app定时开:上一次设置。
断电记忆断电后为断电状态,再上电恢复上一次设置:
开关状态:默认关
温度设置:上一次设置
温度显示:当前环境温度
灯光模式:上一次设置
设备定时关:默认关闭
app定时关:默认关闭。
app定时开:上一次设置。

2.记忆功能的实现

  • 记忆功能嵌入式方案设定
    我们采用将要保存的数据写入到FLASH闪存中,在需要时再读取出来,从而实现记忆功能。

  • 代码实现
    在已有的开发基础上,增加soc_flash.c文件以及头文件,主要内容为数据写入到flash和从flash读取数据的实现代码。

目前整个工程的文件结构如下:

├── src	
|    ├── tuya_drive
|    |  	└── b3950
|    |    		└── b3950.c           //温度传感器驱动相关
|    |  	└── tm1650
|    |    		└──  tm1650.c           
|    |  	└── soc_flash
|    |    		└──  soc_flash.c           
|    |  	└── rgb_led			//LED控制相关
|    |    		└──  led_color_set.c         
|    |  	└── tc309					//按键驱动相关
|    |    		└── tc309.c    
|    |  	└── interrupt				//外部中断
|    |    		└── interrupt.c    
|    |  	└── gpio_control		//gpio控制相关
|    |    		└── gpio_control.c    
|    |  	└── timer
|    |    		└── timer.c           //定时器相关
|    ├── tuya_device.c             //应用层入口文件
|    ├── tuya_thread.c             //主要线程处理文件
|    └── tuya_dp_process.c            //dp数据触发的主要应用文件
|
├── include				//头文件目录
|    ├── tuya_drive_h
|    |  	└── b3950_h
|    |    		└── b3950.h        
|    |  	└── tm1650_h
|    |    		└──  tm1650.h        
|    |  	└── soc_flash_h
|    |    		└──  soc_flash.h      
|    |  	└── rgb_led_h			//LED控制相关
|    |    		└──  led_color_set.h           
|    |  	└── tc309_h					
|    |    		└── tc309.h    
|    |  	└── interrupt_h				
|    |    		└── interrupt.h    
|    |  	└── gpio_control_h
|    |    		└── gpio_control.h        
|    |  	└── timer_h
|    |    		└── timer.h           
|    ├── tuya_device.h           
|    ├── tuya_thread.h       
|    └── tuya_dp_process.h       
|
└── output              //编译产物

soc_flash.c增加的代码如下:

#include "soc_flash.h"
#include "uf_file.h"
#include "tuya_cloud_error_code.h"

STATIC BOOL_T bSocFlashInitFlag = FALSE;



/**
 * @brief: wifi uf write(a+ mode)
 * @param {IN CHAR_T *pFilename -> file name}
 * @param {IN UCHAR_T *pData -> save data}
 * @param {IN USHORT_T usLen -> save data len}
 * @retval: OPERATE_LIGHT
 */
STATIC INT_T opSocFlashFileWrite(IN CHAR_T *pFilename, IN UCHAR_T *pData, IN USHORT_T usLen)
{
    int opRet = -1;
    uFILE * fp = NULL;
    UINT_T uiOffSet = 0;
    UINT_T uiWriteCnt = 0;
    
    fp = ufopen(pFilename, "a+");
    if(NULL == fp) {
        PR_ERR("uf file %s can't open and write data!", pFilename);
        return OPRT_COM_ERROR;
    }
    
    uiOffSet = ufseek(fp, 0, APP_DP_DATA_OFFSET);
    if(uiOffSet != 0) {
        PR_ERR("uf file %s Set file offset to 0 error!", pFilename);
        return OPRT_COM_ERROR;
    }

    uiWriteCnt = ufwrite(fp, pData, usLen);
    if(uiWriteCnt != usLen) {
        PR_ERR("uf file %s write data error!", pFilename);
        return OPRT_COM_ERROR;
    }

    opRet = ufclose(fp);
    if(opRet != OPRT_OK) {
        PR_ERR("uf file %s close error!", pFilename);
        return opRet;
    }

    return OPRT_OK;
}


/**
 * @brief: soc data save
 * @param {IN SOC_FLASH_SAVE_TYPE_E eDataType -> save type(meaning data kind)}
 * @param {IN UINT_T uiAddr -> this type data address offset}
 * @param {IN UCHAR_T *pData -> save data}
 * @param {IN USHORT_T usLen -> save data len}
 * @retval: OPERATE_LIGHT
 */
INT_T opSocFlashWrite(IN SOC_FLASH_SAVE_TYPE_E eDataType, IN UINT_T uiAddr, IN UCHAR_T *pData, IN USHORT_T usLen)
{
    int opRet = -1;
    CHAR_T cTemp[4] = {0};

    if(bSocFlashInitFlag != TRUE) {

        bSocFlashInitFlag = TRUE;
    }

    if(eDataType >= SAVE_TYP_MAX) {
        PR_ERR("Write soc flash type error!");
        return OPRT_INVALID_PARM;
    }

    /*set data type tag*/
    snprintf(cTemp, 4, "%d", eDataType);
    opRet = opSocFlashFileWrite(cTemp, pData, usLen);
    if(opRet != OPRT_OK) {
        return opRet;
    }
    
    return OPRT_OK;
}

/**
 * @brief: wifi uf read
 * @param {IN CHAR_T *pFilename -> read file name}
 * @param {IN USHORT_T usLen -> read data len}
 * @param {OUT UCHAR_T *pData -> read data}
 * @retval: read data cnt
 */
STATIC INT_T uiSocFlashFileRead(IN CHAR_T *pFilename, IN USHORT_T usLen, OUT UCHAR_T *pData)
{
    int opRet = -1;
    uFILE * fp = NULL;
    INT_T uiReadCnt = 0;

    fp = ufopen(pFilename, "r+");
    if(NULL == fp) {
        PR_ERR("uf file %s can't open and read data!", pFilename);
        return OPRT_COM_ERROR;
    }

    PR_DEBUG("uf open OK");
    uiReadCnt = ufread(fp, pData, usLen);
    PR_DEBUG("uf file %s read data %d!", pFilename, uiReadCnt);

    opRet = ufclose(fp);
    if(opRet != OPRT_OK) {
        PR_ERR("uf file %s close error!", pFilename);
        return opRet;
    }
    
    return uiReadCnt;
}

/**
 * @brief: soc flash save data read
 * @param {IN SOC_FLASH_SAVE_TYPE_E eDataType -> read data type(meaning data kind)}
 * @param {IN UINT_T uiAddr -> this type data address offset}
 * @param {IN USHORT_T ucLen -> read data len}
 * @param {OUT UCHAR_T *pData -> read data}
 * @retval: read data cnt
 */
INT_T uiSocFlashRead(IN SOC_FLASH_SAVE_TYPE_E eDataType, IN UINT_T uiAddr, IN USHORT_T usLen, OUT UCHAR_T *pData)
{
    int opRet = -1;
    INT_T uiReadCnt = 0;
    CHAR_T cTemp[4] = {0};
    
    if(bSocFlashInitFlag != TRUE) {
        opRet = uf_file_app_init("12345678901234567890123456789012", 32);
        if(opRet != OPRT_OK) {
            PR_ERR("uf file init error! can't write or read!");
            return opRet;
        }
        bSocFlashInitFlag = TRUE;
    }

    if(eDataType >= SAVE_TYP_MAX) {
        PR_ERR("Read soc flash type error!");
        return OPRT_INVALID_PARM;
    }
    
    snprintf(cTemp, 4, "%d", eDataType);
    PR_DEBUG("file name %s",cTemp);
    uiReadCnt = uiSocFlashFileRead(cTemp, usLen, pData);

    return uiReadCnt;
}


/**
 * @brief: soc flash special block delete
 * @param {none}
 * @retval: OPERATE_LIGHT
 */
INT_T opSocFlashEraseSpecial(IN SOC_FLASH_SAVE_TYPE_E DataType, IN UINT_T addr)
{
    int opRet = 0;
    CHAR_T cTemp[4] = {0};
    
    if(bSocFlashInitFlag != TRUE) {
        return OPRT_OK;     /* directly return */
    }
    snprintf(cTemp, 4, "%d", DataType);
    opRet = ufdelete(cTemp);
    if(opRet != OPRT_OK) {
        PR_ERR("Delete %s file error!", cTemp);
   }

    return OPRT_OK;

}

/**
 * @brief: soc flash erase all
 * @param {none}
 * @retval: OPERATE_LIGHT
 */
INT_T opSocFlashErase(VOID)
{
    int opRet = -1;
    CHAR_T cTemp[4] = {0};
    
    if(bSocFlashInitFlag != TRUE) {
        return OPRT_OK;     /* directly return */
    }
    snprintf(cTemp, 4, "%d", SAVE_TYP1);
    opRet = ufdelete(cTemp);
    if(opRet != OPRT_OK) {
        PR_ERR("Delete %s file error!", cTemp);
    }
    snprintf(cTemp, 4, "%d", SAVE_TYP2);
    opRet = ufdelete(cTemp);
    if(opRet != OPRT_OK) {
        PR_ERR("Delete %s file error!", cTemp);
    }
    
    snprintf(cTemp, 4, "%d", SAVE_TYP3);
    opRet = ufdelete(cTemp);
    if(opRet != OPRT_OK) {
        PR_ERR("Delete %s file error!", cTemp);
    }
    
    snprintf(cTemp, 4, "%d", SAVE_TYP4);
    opRet = ufdelete(cTemp);
    if(opRet != OPRT_OK) {
        PR_ERR("Delete %s file error!", cTemp);
    }

    opRet = ufdelete("oem_cfg");
    if(opRet != OPRT_OK) {
        PR_ERR("Delete oem_cfg file error!");
    }

    return OPRT_OK;

}

  • 代码使用说明:
//创建了一个dp数据保存的结构体
Data_Memory_T dp_memory_s = {
    .switch_bool = 0,
    .led_mode = 0,
    .relay_mode = 0,
    .temperature_value = 0,
    .shake_bool = 0,
    .set_temper_value = 0,
    .time_on_bool = 0,
    .time_off_bool = 0,
    .status = 0,
};
 //创建了一个flash写入线程,接受到信号量后会将dp_memory_s结构写入flash
void flash_write_thread(void)
{
    while(1)
    {
        tuya_hal_semaphore_wait(g_flash_binsemap);
        //写入数据到flash
        opUserFlashWriteAppData(&dp_memory_s);

    }
}
/*上电后会执行记忆恢复函数,将dp_memory_s数据读出,根据读出的数据恢复断电前使用者的
暖风机控制*/
VOID_T Power_data_recovery(void)
{
    Data_Memory_T *power_down_memory = (Data_Memory_T *)Malloc(SIZEOF(Data_Memory_T));
    memset(power_down_memory,0,sizeof(Data_Memory_T));  
    uiSocFlashRead(SAVE_TYP1, APP_DP_DATA_OFFSET, sizeof(Data_Memory_T), (UCHAR_T *)(power_down_memory));

    dp_memory_s.led_mode = power_down_memory->led_mode;
    dp_memory_s.switch_bool = 0;
    dp_memory_s.set_temper_value = power_down_memory->set_temper_value;
    //device timer close clear
    dev_key_s.temp_time_count = 0;
    dev_key_s.timer_hour = 0;
    Free(power_down_memory);
}


此时断电记忆功能已经实现,暖风机功能又完善了一些,后面我们将为暖风机进行赋能,实现智能控制。


技术支持

您可以通过以下方法获得涂鸦的支持:

以上是关于涂鸦智能暖风机软件实现之LED驱动和断电记忆功能的主要内容,如果未能解决你的问题,请参考以下文章

涂鸦智能暖风机软件实现之暖风机外设驱动实现

涂鸦智能暖风机软件实现之利用TM1650实现显示功能

涂鸦智能暖风机改造——硬件搭建

涂鸦智能暖风机软件实现之利用B3950实现温度采集功能

涂鸦智能暖风机软件实现之利用TC309实现触摸按键控制功能

涂鸦智能烧水壶软件实现之水温调节和灯光提示