完成全功能智慧植物生長系統以後,我們就可以為它賦能了,讓它活起來。
建立産品
首先進入塗鴉智能IoT平台,點選建立産品。選擇小家電->寵物->植物生長機。
- 選擇自定義方案,輸入産品名稱,選擇通訊協定為WIFI+藍牙,點選建立産品。
- 添加标準功能,選擇“開關”、“水泵開關”、“目前溫度”、“目前濕度”、“倒計時”、“倒計時剩餘時間”、“故障告警”,功能點名稱、枚舉值等可自行編輯修改。
- 要實作所有的裝置功能,還需要根據功能需求自行建立額外的DP功能點。點選添加功能按鈕,編輯功能點名稱、辨別名,勾選資料類型和資料傳輸類型即可完成功能點建立。
- 添加最大溫度、最小溫度、最大濕度、最小濕度四個DP點,用于設定植物生長機的溫濕度控制區間。
- 添加水箱水量,數值型隻上報類DP點,用于上報水箱内水量的剩餘情況。
- 添加燈光顔色,枚舉型DP點,用于控制補光燈顔色。
- 添加自動補光,布爾型DP點,用于控制裝置切換手動定時補光模式和自動補光模式。
- 設定完功能點後,下一步點選裝置面闆,選擇app的面闆樣式。推薦選擇開發調試面闆,比較直覺,且可以開到dp資料包的接收和發送,友善開發階段調試使用。
- 至此,産品的建立基本完成,可以正式開始嵌入式軟體部分的開發。
嵌入式開發
嵌入式代碼基于BK7231平台,使用塗鴉通用Wi-Fi SDK進行SOC開發,具體環境可以拉取塗鴉git庫上的demo例程或者直接下載下傳已經包含了SDK環境的demo例程。
下載下傳方式一:ty_iot_wf_bt_sdk_bk7231t* GitHub 倉庫
下載下傳方式二:在GitHub了解代碼詳情
1.應用層入口
将庫克隆至本地後,打開檔案目錄,找到apps檔案夾,該檔案夾用于存放應用層代碼,也就是demo的代碼。在這裡建立一個檔案夾,命名為 bk7231t_plant_grow_mach_demo,與demo有關的所有源檔案、頭檔案以及編譯産物都将放到該檔案夾中。
若為初次進行該平台的開發,為求快速上手,推薦将apps檔案下的bk7231t_bl0973_1_plug_demo中的tuya_device.c、tuya_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;
}
-
上報所有DP資料app_report_all_dp_status()
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資料,通過識别DP id來進行相應的資料接收處理_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_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 #4CAF50的plant_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 #4CAF50的
deal_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 #4CAF50的plant_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 #F44336、ADD_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 #F44336 APP_NAME #F44336 APP_VERSION #F44336:
若出現下圖所示提示,則表示編譯成功,固件已經生成:
固件生成路徑為:apps->APP_PATH #F44336->output
将固件燒錄至模組即可開始功能調試階段,有關燒錄和授權方式請參照文檔:WB系列模組燒錄授權
賦能完成
程式燒錄完以後,我們就完成了賦能部分,之後我們就可以對各個功能進行調試了。
全功能智慧植物生長系統更多支援,請點選以下網站:
塗鴉開發者中心:http://developer.tuya.com
塗鴉幫助中心:http://support.tuya.com/cn/help
塗鴉技術支援工單中心:http://service.sonsole.tuya.com
戳我:更多物聯網應用在這裡!