涂鸦智能全功能智慧植物生长系统——《赋能篇》

  完成全功能智慧植物生长系统以后,我们就可以为它赋能了,让它活起来。

创建产品

  • 首先进入涂鸦智能IoT平台,点击创建产品。选择小家电->宠物->植物生长机。

    涂鸦智能全功能智慧植物生长系统——《赋能篇》

  • 选择自定义方案,输入产品名称,选择通讯协议为WIFI+蓝牙,点击创建产品。

  • 添加标准功能,选择“开关”、“水泵开关”、“当前温度”、“当前湿度”、“倒计时”、“倒计时剩余时间”、“故障告警”,功能点名称、枚举值等可自行编辑修改。

    涂鸦智能全功能智慧植物生长系统——《赋能篇》

  • 要实现所有的设备功能,还需要根据功能需求自行创建额外的DP功能点。点击添加功能按钮,编辑功能点名称、标识名,勾选数据类型和数据传输类型即可完成功能点创建。

    • 添加最大温度最小温度最大湿度最小湿度四个DP点,用于设置植物生长机的温湿度控制区间。

    • 添加水箱水量,数值型只上报类DP点,用于上报水箱内水量的剩余情况。

    • 添加灯光颜色,枚举型DP点,用于控制补光灯颜色。

    • 添加自动补光,布尔型DP点,用于控制设备切换手动定时补光模式和自动补光模式。

      涂鸦智能全功能智慧植物生长系统——《赋能篇》

  • 设定完功能点后,下一步点击设备面板,选择app的面板样式。推荐选择开发调试面板,比较直观,且可以开到dp数据包的接收和发送,方便开发阶段调试使用。

    至此,产品的创建基本完成,可以正式开始嵌入式软件部分的开发。

嵌入式开发

嵌入式代码基于BK7231平台,使用涂鸦通用Wi-Fi SDK进行SOC开发,具体环境可以拉取涂鸦git库上的demo例程或者直接下载已经包含了SDK环境的demo例程。

1.应用层入口

将库克隆至本地后,打开文件目录,找到apps文件夹,该文件夹用于存放应用层代码,也就是demo的代码。在这里创建一个文件夹,命名为 bk7231t_plant_grow_mach_demo,与demo有关的所有源文件、头文件以及编译产物都将放到该文件夹中。

若为初次进行该平台的开发,为求快速上手,推荐将apps文件下的bk7231t_bl0973_1_plug_demo中的tuya_device.ctuya_device.h文件都复制过来。

打开tuya_device.c文件,找的device_init #4CAF50函数:

OPERATE_RET device_init(VOID)
{
  OPERATE_RET op_ret = OPRT_OK;

  TY_IOT_CBS_S wf_cbs = {
      status_changed_cb,\
      gw_ug_inform_cb,\
      gw_reset_cb,\
      dev_obj_dp_cb,\
      dev_raw_dp_cb,\
      dev_dp_query_cb,\
      NULL,
  };

  op_ret = tuya_iot_wf_soc_dev_init_param(hw_get_wifi_mode(),WF_START_SMART_FIRST,\
                                          &wf_cbs,NULL,PRODECT_KEY,DEV_SW_VERSION);
  if(OPRT_OK != op_ret) {
      PR_ERR("tuya_iot_wf_soc_dev_init_param error,err_num:%d",op_ret);
      return op_ret;
  }

  op_ret = tuya_iot_reg_get_wf_nw_stat_cb(wf_nw_status_cb);
  if(OPRT_OK != op_ret) {
      PR_ERR("tuya_iot_reg_get_wf_nw_stat_cb is error,err_num:%d",op_ret);
      return op_ret;
  }

  op_ret= app_dltj_init(APP_DLTJ_NORMAL);
  if(OPRT_OK != op_ret) {
      PR_ERR("dltj init err!");
      return op_ret;
  }

  op_ret = app_switch_init(APP_SW_MODE_NORMAL);
  if(op_ret != OPRT_OK) {
      return op_ret;
  }

  return op_ret;
}

在BK7231t平台的SDK环境中,该函数为重要的应用代码入口,设备上电后平台适配层运行完一系列初始化代码后就会调用该函数来进行应用层的初始化操作。

该函数做了三件事:

  • 调用tuya_iot_wf_soc_dev_init_param()接口进行SDK初始化,配置了工作模式、配网模式,同时注册了各种回调函数并存入了固件key和PID。
   TY_IOT_CBS_S wf_cbs = {
       status_changed_cb,\
       gw_ug_inform_cb,\
       gw_reset_cb,\
       dev_obj_dp_cb,\
       dev_raw_dp_cb,\
       dev_dp_query_cb,\
       NULL,
   };

   op_ret = tuya_iot_wf_soc_dev_init_param(hw_get_wifi_mode(),WF_START_SMART_FIRST,\
                                           &wf_cbs,NULL,PRODECT_KEY,DEV_SW_VERSION);
   if(OPRT_OK != op_ret) {
       PR_ERR("tuya_iot_wf_soc_dev_init_param error,err_num:%d",op_ret);
       return op_ret;
   }	 
  • 调用tuya_iot_reg_get_wf_nw_stat_cb()接口注册设备网络状态回调函数。
   op_ret = tuya_iot_reg_get_wf_nw_stat_cb(wf_nw_status_cb);
   if(OPRT_OK != op_ret) {
       PR_ERR("tuya_iot_reg_get_wf_nw_stat_cb is error,err_num:%d",op_ret);
       return op_ret;
   }
  • 调用应用层初始化函数
   op_ret= app_dltj_init(APP_DLTJ_NORMAL);
   if(OPRT_OK != op_ret) {
       PR_ERR("dltj init err!");
       return op_ret;
   }

   op_ret = app_switch_init(APP_SW_MODE_NORMAL);
   if(op_ret != OPRT_OK) {
       return op_ret;
   }

由于该文件是从别的demo中复制过来的,所以这里调用的也是别的demo的函数。此时就需要再实现一个本demo自己的应用初始化函数:新建一个app_plant.c #4CAF50和对应头文件,实现app_plant_init() #4CAF50函数,然后在device_init #4CAF50中调用。

2.应用结构

本demo应用代码主要分三层来实现:

  • 最底层为各个传感器的驱动代码,封装出传感器的初始化、采集等接口;
  • 第二层为控制逻辑部分的代码,调用驱动层的传感器接口,实现各个组件的控制逻辑,封装出数据处理轮询接口;
  • 第一层为主要应用层,创建应用任务调用第二层的接口,同时处理DP点数据的上报和接受解析。

第一层就是在app_plant.c #4CAF50文件中实现的,大致内容如下:

  • app_plant_init() 调用第二层封装出的设备初始化接口,创建应用任务;
OPERATE_RET app_plant_init(IN APP_PLANT_MODE mode)
{
    OPERATE_RET op_ret = OPRT_OK;


    if(APP_PLANT_NORMAL == mode) {
        
		// IO、传感器、pwm等初始化
        plant_device_init();
        
        // 创建I2C类传感器数据采集任务
        xTaskCreate(sensor_data_get_iic_theard,"thread_data_get_iic",512,NULL,TRD_PRIO_3,NULL);

        // 创建ADC类传感器数据采集任务
        xTaskCreate(sensor_data_get_adc_theard,"thread_data_get_adc",512,NULL,TRD_PRIO_4,NULL);

        // 创建数据处理任务
        xTaskCreate(sensor_data_deal_theard,"thread_data_deal",512,NULL,TRD_PRIO_4,NULL);

        // 创建dp点数据定时循环上报任务
        xTaskCreate(sensor_data_report_theard,"thread_data_report",512,NULL,TRD_PRIO_4,NULL);
    }else {
        // 非产测模式
    }

    return op_ret;
}
  • app_report_all_dp_status()上报所有DP数据
VOID app_report_all_dp_status(VOID)
{
   OPERATE_RET op_ret = OPRT_OK;

   INT_T dp_cnt = 0;
   dp_cnt = 12;

   TY_OBJ_DP_S *dp_arr = (TY_OBJ_DP_S *)Malloc(dp_cnt*SIZEOF(TY_OBJ_DP_S));
   if(NULL == dp_arr) {
       PR_ERR("malloc failed");
       return;
   }

   memset(dp_arr, 0, dp_cnt*SIZEOF(TY_OBJ_DP_S));

   dp_arr[0].dpid = DPID_SWITCH_P;
   dp_arr[0].type = PROP_BOOL;
   dp_arr[0].time_stamp = 0;
   dp_arr[0].value.dp_value = plant_ctrl_data.Switch;
   ......
   
   op_ret = dev_report_dp_json_async(NULL,dp_arr,dp_cnt);
   Free(dp_arr);
   if(OPRT_OK != op_ret) {
       PR_ERR("dev_report_dp_json_async relay_config data error,err_num",op_ret);
   }

   PR_DEBUG("dp_query report_all_dp_data");
   return;
}
  • 任务函数,任务内循环调用的plant_get_iic_sensor_data()plant_get_adc_sensor_data()plant_ctrl_handle()plant_ctrl_all_off()都是第二层的接口,实现在plant_control.c #4CAF50文件中
STATIC VOID sensor_data_get_iic_theard(PVOID_T pArg)
{   
    while(1) {

        PR_DEBUG("plant_get_i2c_sensor_data");
        vTaskDelay(TASKDELAY_SEC);

        if(TRUE == plant_ctrl_data.Switch) {    
            plant_get_iic_sensor_data();
        }
        
    }
}

STATIC VOID sensor_data_get_adc_theard(PVOID_T pArg)
{   
    while(1) {

        PR_DEBUG("plant_get_adc_sensor_data");
        vTaskDelay(TASKDELAY_SEC*2);

        if(TRUE == plant_ctrl_data.Switch) {
            plant_get_adc_sensor_data();
        }
        
    }
}

STATIC VOID sensor_data_deal_theard(PVOID_T pArg)
{   
    while(1) {
        vTaskDelay(TASKDELAY_SEC);

        if(TRUE == plant_ctrl_data.Switch) {
            plant_ctrl_handle();
        }else {
            plant_ctrl_all_off();
        }
        
    }

}

STATIC VOID sensor_data_report_theard(PVOID_T pArg)
{   
    while(1) {
        vTaskDelay(TASKDELAY_SEC*5);
        app_report_all_dp_status();
    }

}
  • deal_dp_proc()处理接受到的DP数据,通过识别DP id来进行相应的数据接收处理
VOID deal_dp_proc(IN CONST TY_OBJ_DP_S *root)
{
    UCHAR_T dpid;

    dpid = root->dpid;
    PR_DEBUG("dpid:%d",dpid);
    
    switch (dpid) {
    
    case DPID_SWITCH_P:
        PR_DEBUG("set switch:%d",root->value.dp_bool);
        plant_ctrl_data.Switch = root->value.dp_bool;
        break;
        
    case DPID_PUMP:
        PR_DEBUG("set pump:%d",root->value.dp_bool);
        plant_ctrl_data.Pump = root->value.dp_bool;
        break;
        
		......
		
    default:
        break;
    }

    return;

}

实现了上述的几个函数后,应用层代码的大概结构就已经确定下来了,接下来就需要实现上面提到的被调用的第二层接口,这些接口都放在本demo的plant_control.c #4CAF50文件中。在下面的内容里,本篇文档将按照温湿度、光照、土壤水份等不同的控制功能闭环来一步步解说demo例程。

3.温湿度控制

要实现温湿度控制,首先要做的就是采集到温湿度。本demo方案采集温湿度的方式是使用SHT21温湿度传感器,该传感器是IIC协议通讯,因此首先需要根据该传感器的技术手册编写传感器驱动代码。在完成驱动代码后,再封装出传感器的初始化、数据采集、数据换算等接口。本demo有关SHT21传感器的驱动和外部接口都实现在sht21.c #4CAF50文件中,封装的外部接口都在plant_control.c #4CAF50中被调用:

  • tuya_sht21_init(sht21_init_t* param)传感器初始化,传参为一个包含SDA、SCL对应IO口和解析度的结构体的指针;
typedef struct 
{
    UCHAR_T SDA_PIN;            ///< SDA pin
    UCHAR_T SCL_PIN;            ///< SCL pin
    sht21_resolution_t RESOLUTION;   
}sht21_init_t;

plant_control.c #4CAF50中定义这个结构体变量,并在plant_device_init()里调用初始化:

#define IIC_SDA_PIN                         (6)
#define IIC_SCL_PIN                         (7)

STATIC sht21_init_t sht21_int_param = {IIC_SDA_PIN, IIC_SCL_PIN, SHT2x_RES_10_13BIT};

VOID plant_device_init(VOID)
{
   
    // SHT21 IIC driver init 
    tuya_sht21_init(&sht21_int_param);
	
}

初始化完成后,就可以获取环境温湿度了。因为植物生长机需要不断的获取环境参数,所以代码上也需要不断的使用传感器数据获取接口。在上一节内容中有提到在app_plant.c #4CAF50文件中有创建过各个功能逻辑任务,其中有一个任务函数循环调用了plant_control.c #4CAF50plant_get_iic_sensor_data(),所以这里需要在该接口中调用SHT21传感器的数据采集接口tuya_sht21_measure()和计算接口tuya_sht21_cal_RH(),两个接口的传参都为一个用于切换是获取温度还是湿度的枚举值:

VOID plant_get_iic_sensor_data(VOID)
{

    SHORT_T hum;
    SHORT_T temp;

    tuya_sht21_init(&sht21_int_param);

    hum = tuya_sht21_measure(HUMIDITY);
    device_data.humidity = tuya_sht21_cal_RH(hum);
	
    if(device_data.humidity > 0){ // 剔除小于0的无效湿度值
        plant_report_data.Humidity_current = (UCHAR_T)device_data.humidity;
        PR_NOTICE("humidity = %d",plant_report_data.Humidity_current);
    }

    temp = tuya_sht21_measure(TEMP);
    device_data.temperature = tuya_sht21_cal_temperature(temp);
    plant_report_data.Temp_current = (UCHAR_T)device_data.temperature;
    PR_NOTICE("tempre = %d",plant_report_data.Temp_current);
        
        

}

获取到环境温湿度值后,就可以和温湿度设定值做一个判断。在创建产品的时候新建了最大最小温度和最大最小湿度四个自定义功能点,所以可以在app上进行设置,并通过云端下发给设备。下发的DP点数据在app_plant.c #4CAF50deal_dp_proc()函数中进行处理:

VOID deal_dp_proc(IN CONST TY_OBJ_DP_S *root)
{
    UCHAR_T dpid;

    dpid = root->dpid;
    PR_DEBUG("dpid:%d",dpid);
    
    switch (dpid) {
   
    ......
	
    case DPID_TEMP_MAX:
        PR_DEBUG("set temp max:%d",root->value.dp_value);
        plant_ctrl_data.Temp_max = root->value.dp_value;
        break;
    
    case DPID_HUMIDITY_MAX:
        PR_DEBUG("set humidity max:%d",root->value.dp_value);
        plant_ctrl_data.Humidity_max = root->value.dp_value;
        break;
    
    case DPID_TEMP_MIN:
        PR_DEBUG("set temp min:%d",root->value.dp_value);
        plant_ctrl_data.Temp_min = root->value.dp_value;
        break;

    case DPID_HUMIDITY_MIN:
        PR_DEBUG("set humidity min:%d",root->value.dp_value);
        plant_ctrl_data.Humidity_min = root->value.dp_value;
        break;
    ......
	
    default:
        break;
    }

    return;

}

app_plant.c #4CAF50中创建过一个任务用于数据判断和IO设备控制,该任务循环调用了plant.control.c #4CAF50中的plant_ctrl_handle()函数,所有有关具体的控制逻辑实现都放在plant_ctrl_handle()中。本demo方案用于控制温湿度的器件为一个加湿器、一个加热灯和一个风扇,通过继电器实现用IO口的高低电平控制这些器件的开和关。控制IO口电平需要用到SDK封装好的接口tuya_gpio_inout_set()tuya_gpio_write()。温湿度控制相关代码如下:

#define HUMIDIFIER_PORT                     (24)
#define HUMIDIFIER_LEVEL                    LOW

#define HEATING_ROD_PORT                    (20)
#define HEATING_ROD_LEVEL                   LOW

#define COOL_DOWN_FAN_PORT                  (21)
#define COOL_DOWN_FAN_LEVEL                 LOW

STATIC VOID __ctrl_gpio_init(CONST TY_GPIO_PORT_E port, CONST BOOL_T high)
{
    // 设置IO口为输出模式
    tuya_gpio_inout_set(port, FALSE);
	// 设置IO口电平
    tuya_gpio_write(port, high);
}

VOID plant_device_init(VOID)
{
    // SHT21 IIC driver init 
    tuya_sht21_init(&sht21_int_param);

    // gpio init
    __ctrl_gpio_init(HUMIDIFIER_PORT, HUMIDIFIER_LEVEL);

    __ctrl_gpio_init(COOL_DOWN_FAN_PORT, COOL_DOWN_FAN_LEVEL);  

    __ctrl_gpio_init(HEATING_ROD_PORT, HEATING_ROD_LEVEL);

}

STATIC VOID __passive_ctrl_module_temp_humidity(VOID)
{   
    if(device_data.humidity < plant_ctrl_data.Humidity_min) {
        tuya_gpio_write(HUMIDIFIER_PORT, !HUMIDIFIER_LEVEL);
    }else {
        tuya_gpio_write(HUMIDIFIER_PORT, HUMIDIFIER_LEVEL);
    }

    if(device_data.temperature < plant_ctrl_data.Temp_min) {
        tuya_gpio_write(HEATING_ROD_PORT, !HEATING_ROD_LEVEL);
    }else {
        tuya_gpio_write(HEATING_ROD_PORT, HEATING_ROD_LEVEL);
    }

    if((device_data.temperature > plant_ctrl_data.Temp_max)||(device_data.humidity > plant_ctrl_data.Humidity_max)) {
        tuya_gpio_write(COOL_DOWN_FAN_PORT,!COOL_DOWN_FAN_LEVEL);
    }else {
        tuya_gpio_write(COOL_DOWN_FAN_PORT,COOL_DOWN_FAN_LEVEL);
    }
    
}

VOID plant_ctrl_handle(VOID)
{   
    PR_DEBUG("enter ctrl handle");

    __passive_ctrl_module_temp_humidity();
	
}

4.光照控制

本demo方案使用BH1750照度检测传感器,和温湿度传感器一样也是IIC协议的。根据该传感器的数据手册编写传感器驱动代码。在完成驱动代码后,封装出传感器的初始化、数据采集等接口。本demo有关BH1750传感器的驱动和外部接口都实现在bh1750.c #4CAF50文件中,封装的外部接口都在plant_control.c #4CAF50中被调用:

  • tuya_bh1750_init(sht21_init_t* param)传感器初始化,传参为一个包含SDA、SCL对应IO的结构体的指针;
typedef struct 
{
    UCHAR_T SDA_PIN;            ///< SDA pin
    UCHAR_T SCL_PIN;            ///< SCL pin 
}bh1750_init_t;

plant_control.c #4CAF50中定义这个结构体变量,并在plant_device_init()里调用初始化:

#define IIC_SDA_PIN                         (6)
#define IIC_SCL_PIN                         (7)

STATIC bh1750_init_t bh1750_int_param = {IIC_SDA_PIN, IIC_SCL_PIN};

VOID plant_device_init(VOID)
{
   ......
    // SHT21 IIC driver init 
    tuya_bh1750_init(&bh1750_int_param);
   ......
}

初始化完成后,在plant_get_iic_sensor_data()函数里调用BH1750传感器的数据采集接口tuya_bh1750_get_bright_value()获取光照强度值。由于调试过程中发现光照传感器和温湿度传感器的采集中间不加延时的话会影响通讯,因此做了点改动让每次进入plant_get_iic_sensor_data()函数时只启用其中一个传感器:

VOID plant_get_iic_sensor_data(VOID)
{

    SHORT_T hum;
    SHORT_T temp;

    switch (IIC_SELECT_FLAG)
    {
    case 0:    
        tuya_sht21_init(&sht21_int_param);

        hum = tuya_sht21_measure(HUMIDITY);
        device_data.humidity = tuya_sht21_cal_RH(hum);
        if(device_data.humidity > 0){ // 剔除小于0的无效湿度值
            plant_report_data.Humidity_current = (UCHAR_T)device_data.humidity;
            PR_NOTICE("humidity = %d",plant_report_data.Humidity_current);
        }

        temp = tuya_sht21_measure(TEMP);
        device_data.temperature = tuya_sht21_cal_temperature(temp);
        plant_report_data.Temp_current = (UCHAR_T)device_data.temperature;
        PR_NOTICE("tempre = %d",plant_report_data.Temp_current);
        
        IIC_SELECT_FLAG = 1;

        break;
    case 1:    
        tuya_bh1750_init(&bh1750_int_param);

        device_data.light_intensity_value = tuya_bh1750_get_bright_value();
        PR_NOTICE("light_intensity_value = %d",device_data.light_intensity_value);
        
        IIC_SELECT_FLAG = 0;

        break;

    default:
        break;
    }

}

光照控制方面的设定值没有创建对应的DP点,无法用app设置,只在代码内写死了一个数值,通过控制灯光亮度来使光照传感器采集值不断逼近该数值,并给定了一个误差范围值来防止在亮度临界点时发生的灯光不断闪烁的现象。

#define ACCURACY                         (2000)  // 误差范围值
#define light_value_set                  (12000) // 亮度设定值 unit:lux

本demo方案通过输出PWM波的方式来控制灯板的亮度,有关PWM的初始化和输出控制函数接口都实现在plant_pwm.c #4CAF50中,在plant_device_init()中初始化PWM,并在plant_ctrl_handle()中调用实现灯光控制逻辑的接口:

USER_PWM_DUTY_T user_pwm_duty = {0,0};

VOID plant_device_init(VOID)
{
    ......
    plant_pwm_init();
    ......
}

STATIC VOID __passive_ctrl_module_light(VOID)
{   
    if(IIC_SELECT_FLAG){ // 若上一次启用的iic传感器为温湿度传感器
        return;
    }

    if((TRUE == plant_ctrl_data.Auto_switch)) { // 自动补光开关为开
        USHORT_T current = device_data.light_intensity_value;
        USHORT_T set = light_value_set;

        if((current - set) > ACCURACY) { // 当前光照强度大于设定值且不在误差范围内
            if((current - set) >= 200) {
                if(plant_ctrl_data.Bright_value >= 50)plant_ctrl_data.Bright_value -= 50;
            }else if((current - set) > 150) {
                if(plant_ctrl_data.Bright_value >= 20)plant_ctrl_data.Bright_value -= 20;
            }else {
                if(plant_ctrl_data.Bright_value >= 1)plant_ctrl_data.Bright_value--;
            }
        }else if((set - current) > ACCURACY) { // 当前光照强度小于设定值且不在误差范围内
            if((set - current) >= 200) {
                if(plant_ctrl_data.Bright_value <= 950)plant_ctrl_data.Bright_value += 50;
            }else if((set - current) > 150) {
                if(plant_ctrl_data.Bright_value <= 980)plant_ctrl_data.Bright_value += 20;
            }else {
                if(plant_ctrl_data.Bright_value <= 999)plant_ctrl_data.Bright_value++;
            }
        }
    } 
}

STATIC VOID __initiative_ctrl_module_light(VOID)
{   
    
    if(TRUE == plant_ctrl_data.Auto_switch) { // 自动补光开关为开
        PR_NOTICE("Ligth open !!!!");
        if(plant_ctrl_data.Light_color == red) { // 灯光颜色设为红灯
            user_pwm_duty.duty_red = plant_ctrl_data.Bright_value;
            user_pwm_duty.duty_blue = 0;
        }else if(plant_ctrl_data.Light_color == blue) { // 灯光颜色设为蓝灯
            user_pwm_duty.duty_blue = plant_ctrl_data.Bright_value;
            user_pwm_duty.duty_red = 0;
        }else {
            user_pwm_duty.duty_blue = plant_ctrl_data.Bright_value;
            user_pwm_duty.duty_red = user_pwm_duty.duty_blue;
        }
        plant_pwm_set(&user_pwm_duty);
    }else { // 自动补光开关为关 用户手动定时控制
        if(plant_ctrl_data.Light_color == red) {
            user_pwm_duty.duty_red = 1000;
            user_pwm_duty.duty_blue = 0;
        }else if(plant_ctrl_data.Light_color == blue) {
            user_pwm_duty.duty_blue = 1000;
            user_pwm_duty.duty_red = 0;
        }else {
            user_pwm_duty.duty_red = 1000;
            user_pwm_duty.duty_blue = 1000;
        }
        if((IsThisSysTimerRun(light_timer) == FALSE)&&(plant_ctrl_data.Countdown_set != cancel)) {
            light_flag_min = (USHORT_T)plant_ctrl_data.Countdown_set * 60;
            plant_pwm_set(&user_pwm_duty);
            sys_start_timer(light_timer,1000*60,TIMER_CYCLE);
        }else if(plant_ctrl_data.Countdown_set == cancel) {
            user_pwm_duty.duty_blue = 0;
            user_pwm_duty.duty_red = 0;
            plant_pwm_set(&user_pwm_duty);
            light_flag_min = 0;
            sys_stop_timer(light_timer);
        }else if(IsThisSysTimerRun(light_timer) == TRUE) {
            plant_pwm_set(&user_pwm_duty);
        }
		// 保存定时剩余时间 单位分钟
        plant_report_data.Countdown_left = light_flag_min;
    }
                                                                           
}

VOID plant_ctrl_handle(VOID)
{   
    ......
    __passive_ctrl_module_light();
    __initiative_ctrl_module_light();
}

5.土壤湿度控制

本demo方案使用的土壤湿度检测传感器可以根据土壤的湿度情况输出模拟量,因此代码上就需要通过ADC采集模拟量转换为数字量的方式来监测土壤湿度。
app_plant.c #4CAF50文件中创建的获取ADC采集任务中循环调用了plant_control.c #4CAF50plant_get_adc_sensor_data(),所有有关adc采集的代码都放在该函数接口内:

VOID plant_get_adc_sensor_data(VOID)
{   // 控制开关模拟芯片选择土壤湿度的通道
    rs2255_channel_checkout(SOIL_MOISTURE_SENSOR_PORT);
		
    tuya_hal_adc_init(&tuya_adc);
    tuya_hal_adc_value_get(TEMP_ADC_DATA_LEN, &device_data.soil_humidity);
    PR_NOTICE("soil_humidity = %d",device_data.soil_humidity);
 
    tuya_hal_adc_finalize(&tuya_adc);

}

获取土壤湿度值后,根据湿度情况控制水泵是否打开来实现自动浇水。在plant_ctrl_handle()中调用实现控制浇水逻辑的接口:

  • 引入了ADD_WATER_COUNT #F44336ADD_WATER_READY #F44336两个变量,实现水泵每开启一段时间后就会关闭一段时间,防止浇水过度。
STATIC VOID __passive_ctrl_module_soil_humidity(VOID)
{   
    if(device_data.soil_humidity > plant_ctrl_data.Soil_humidity_threshold) { 

        if(ADD_WATER_READY) { 

            tuya_gpio_write(WATER_VALVE_PORT, !WATER_VALVE_LEVEL);

            ADD_WATER_COUNT++;
            if(ADD_WATER_COUNT > 5) {
                ADD_WATER_READY = 0;
            }

        } else{

            tuya_gpio_write(WATER_VALVE_PORT, WATER_VALVE_LEVEL);
            ADD_WATER_COUNT++;
            if(ADD_WATER_COUNT >15) {
                ADD_WATER_READY = 1;
                ADD_WATER_COUNT = 0;
            }

        }
    }else {

        ADD_WATER_READY = 1;
        ADD_WATER_COUNT = 0;
        tuya_gpio_write(WATER_VALVE_PORT, WATER_VALVE_LEVEL);

    }
}

VOID plant_ctrl_handle(VOID)
{   
    ......
    __passive_ctrl_module_soil_humidity();
    ......
}

6.水箱控制

给土壤浇水的水泵是从水箱中抽水,当水箱中的水量过少时则需要另一个水泵给水箱加水。控制水箱自动加水就用到了水位传感器,该传感器也是通过输出模拟量的大小来反映测量区域水位的位置的,因此和土壤湿度传感器一样也使用ADC采集模拟量转换数字量:

  • rs2255_init()为模拟开关芯片的初始化函数,用于解决IO口不够用的问题。由于使用的控制脚和做为SDA、SCL的是同一对IO口,所有在每次采集adc数据时都要重新初始化。IIC传感器的初始化也同理。
VOID plant_get_adc_sensor_data(VOID)
{
    rs2255_init();

    switch (ADC_SELECT_FLAG)
    {
    case 0:    

        rs2255_channel_checkout(WATER_SENSOR_PORT);
        tuya_hal_adc_init(&tuya_adc);
        tuya_hal_adc_value_get(TEMP_ADC_DATA_LEN, &device_data.water_tank_value);
        PR_NOTICE("water_tank_value = %d",device_data.water_tank_value);

        ADC_SELECT_FLAG = 1;

        break;
    case 1:    
        
        rs2255_channel_checkout(SOIL_MOISTURE_SENSOR_PORT);
        tuya_hal_adc_init(&tuya_adc);
        tuya_hal_adc_value_get(TEMP_ADC_DATA_LEN, &device_data.soil_humidity);
        PR_NOTICE("soil_humidity = %d",device_data.soil_humidity);

        ADC_SELECT_FLAG = 0;

        break;

    default:
        break;
    }
    
    tuya_hal_adc_finalize(&tuya_adc);

}

plant_ctrl_handle()中调用实现水箱水量控制的接口:

#define WATER_PUMP_PORT                     (22)
#define WATER_PUMP_LEVEL                    LOW

STATIC VOID __initiative_ctrl_module_pump(VOID)
{   
    // 根据水位传感器值转换剩余水量百分比,用于上报
    if(device_data.water_tank_value < 1700) {
        plant_report_data.Water_remain = 10;
    }else if(device_data.water_tank_value < 2500) {
        plant_report_data.Water_remain = 25;
    }else if(device_data.water_tank_value < 2700) {
        plant_report_data.Water_remain = 50;
    }else if(device_data.water_tank_value < 2900) {
        plant_report_data.Water_remain = 75;
    }else if(device_data.water_tank_value >= 3000) {
        plant_report_data.Water_remain = 100;
    }

    if(TRUE == plant_ctrl_data.Pump){ // 若水泵开关为开
        PR_NOTICE("water pump open !!!!");
        tuya_gpio_write(WATER_PUMP_PORT,!WATER_PUMP_LEVEL);
    }else {
        tuya_gpio_write(WATER_PUMP_PORT,WATER_PUMP_LEVEL);
    }
    if(device_data.water_tank_value >= 3000) { // 当水量接近测量上限时关闭水泵
        PR_NOTICE("water tank is full !!!!");
        tuya_gpio_write(WATER_PUMP_PORT,WATER_PUMP_LEVEL);
        plant_ctrl_data.Pump = FALSE;
    }
                                                                      
}

VOID plant_ctrl_handle(VOID)
{   
    ......
    __initiative_ctrl_module_pump();
    ......
}

至此,本demo的大部分控制逻辑代码就基本完成了,在完善dp点上报和接收部分后即可开始后续的功能调试。想了解demo代码的其他细节请自行查看demo例程。

7.编译和烧录

在linux终端输入指令运行SDK环境目录下的build_app.sh #4CAF50脚本来编译代码生成固件,指令格式为 sh build_app.sh APP_PATH #F44336APP_NAME #F44336APP_VERSION #F44336
涂鸦智能全功能智慧植物生长系统——《赋能篇》

若出现下图所示提示,则表示编译成功,固件已经生成:
涂鸦智能全功能智慧植物生长系统——《赋能篇》

固件生成路径为:apps->APP_PATH #F44336->output

将固件烧录至模组即可开始功能调试阶段,有关烧录和授权方式请参照文档: WB系列模组烧录授权

赋能完成

程序烧录完以后,我们就完成了赋能部分,之后我们就可以对各个功能进行调试了。

上一篇:PHP数组


下一篇:洛谷P1879 [USACO06NOV]玉米田Corn Fields(状压dp)