资讯详情

改造一台可以计算滤芯使用寿命的智能空气净化器——嵌入式功能实现篇

上一篇文章改造了一个智能空气净化器,可以计算滤芯的使用寿命— 介绍智能净化器的硬件改造方案。本文介绍了如何实现智能空气净化器的各种净化模式、滤芯检测和滤芯寿命计算功能。

一. 功能需求

:智能空气净化器通常有三种工作模式:自动、手动和睡眠App切换。风扇转速可根据检测环境中有害物质的浓度自动控制,UV灯、屏幕灯等。也可以通过NFC检测滤芯是否安装,读取滤芯生产信息,智能计算滤芯寿命。

功能 说明
设备功能 1. 主按键: 长按开关机,短按切换不同的工作模式(自动、手动、睡眠)。2. 屏幕按键: 短按切换屏幕和指示灯的状态(PM2.5,PM10,TVOC,温度、湿度、关闭), 长按进入配网模式。3. 显示屏:三位断码屏,通过屏幕按钮显示空气指标PM2.5数据、PM10数据、TVOC数据、温度、湿度。4. 蜂鸣器:按键音和报警提醒。5. 若滤芯仓门打开,则设备断电不工作。6. NFC芯片:每次启动时检测滤芯NFC读取滤芯信息并报告是否存在。
App功能 1. 总开关。2. 童:APP打开后,无法使用设备端按钮。3. 静音:APP打开后,设备端按钮静音。4. 定时:APP设置后,启动定时开关功能。5. 智能杀菌:需要APP手动开启。6. 空气指标显示:PM2.5数据、PM10数据、TVOC数据、温度、湿度。7. 滤芯寿命显示及重置,支持APP也支持手动重置NFC识别后自动重置。8. 工作模式:自动模式、手动模式、睡眠模式。运行自动模式功能的自动模式。本模式下的风速档位可手动设置。睡眠模式,20%的风速进入睡眠档位,屏幕LED灯关闭。9. 风速档位:睡眠(20%),低(40%),中(70%),高(100%)。
智能功能 1. 自动模式-风扇自动调节功能。2. 自动模式-环保功能:当室内空气质量优良(0-35)并保持一段时间(2)h)关闭风扇以节约能源PM2.5如果再次超过35,则启动风扇自动调节。3. 自动模式-睡眠功能:当光传感器检测到房间变暗时,设备会自动关闭灯和屏幕。如果空气质量保持良好(30min),将风扇降至最低档(20%风速)。4. 智能杀菌功能:温度20°湿度超过65%,接近4h杀菌(打开)无触发UV灯),打开UV杀菌持续1h。5. 滤芯NFC检测功能:检测滤芯是否存在,读取滤芯类别和生产信息,防盗版。6. 滤芯寿命智能计算功能:智能计算滤芯粉尘累计吸附量,获得滤芯剩余寿命,并根据粉尘吸附速度和设备使用频率估算滤芯剩余可用时间。
配网功能 通用Wi-Fi BLE配网,长按配网按钮重置配网。

二. 环境搭建

2.1 开发环境建设

(1)开发环境建设可参考Wi-Fi 模块二次开发教程-1. SoC构建开发环境。如果已经有虚拟机和乌班图的开发环境,可以直接跳到剩余环境到处建设。

(2)产品创建可参考Wi-Fi模块二次开发教程-2. 涂鸦IoT平台介绍。创建产品后,添加产品标准功能DP点和自定义功能DP点。空气净化器DP点如下表。

(3)参考Wi-Fi模块二次开发课程-3. 快速上手完成代码修改编译,上传固件token、烧录授权及设备配网。

2.2 功能DP点

DP ID 功能点 标识符 数据传输类型 数据类型 功能点属性
1 开关 switch 可下发可上报 bool
2 PM2.5 pm25 只上报 value 数值范围: 0-700,间距: 1, 倍数: 0,单位: ug/m3
3 模式 mode 可下发可上报 enum 枚举值: manual, auto, sleep
4 风速 fan_speed_enum 可下发可上报 enum 枚举值: sleep, low, mid, high
5 滤芯寿命 filter_life 只上报 value 数值范围: 0-100,间距: 1, 倍数: 0,单位: %
7 童锁 child_lock 可下发可上报 bool
8 灯光 light 可下发可上报 bool
9 UV杀菌 uv 可下发可上报 bool
11 滤芯复位 filter_reset 可下发可上报 bool
12 室内温度 temp_indoor 只上报 value 数值范围: -20-50,间距: 1, 倍数: 0,单位: ℃
13 室内湿度 humidity 只上报 value 数值范围: 0-100,间距: 1, 倍数: 0,单位: %
14 TVOC tvoc 只上报 value 数值范围: 0-999,间距: 1, 倍数: 0,单位: ug/m3
16 滤芯剩余天数 filter_days 只上报 value 数值范围: 0-1000,间距: 1, 倍数: 0,单位: day
17 累计工作时间 runtime_total 只上报 value 数值范围: 0-5256000,间距: 1, 倍数: 0,单位: min
18 倒计时 countdown_set 可下发可上报 enum 枚举值: cancel, 1h, 2h, 3h, 4h, 5h
19 倒计时剩余时间 countdown_left 只上报 value 数值范围: 0-360,间距: 1, 倍数: 0,单位: min
20 累计吸收颗粒 pm_total 只上报 value 数值范围: 0-10000000,间距: 1, 倍数: 0,单位: mg
22 故障告警 fault 只上报 fault 故障值: e1, e2
23 温标切换 temp_unit_convert 可下发可上报 enum 枚举值: c, f
101 PM10 pm10 只上报 value 数值范围: 0-999,间距: 1, 倍数: 0,单位: ug/m3
102 滤芯种类 filter_type 只上报 enum 枚举值: standard, Antibacterial, Aldehyde_removal, Professional
103 静音 sound_switch 可下发可上报 bool

三. 总体设计

3.1 模块划分

对功能需求进行分析梳理后,可将空气净化器demo程序划分为以下八大模块:

No. 模块 处理内容
1 外设驱动组件 按键、电机、段码显示屏、NFC读卡器、空气质量传感器等驱动程序
2 设备基础服务 设备开关、状态处理、模式切换、本地定时等
3 显示处理服务 段码液晶屏显示空气指标数据、LED灯指示段码屏显示的空气指标内容
4 环境检测服务 检测空气质量环境、光线亮暗情况
5 数据计算处理 自动风扇转速算法滤芯寿命和粉尘吸附量的计算和存储、滤芯NFC数据解析
6 用户事件处理 按键事件检测和处理、仓门开关事件检测和处理
7 定时事件处理 各定时事件的判断和处理
8 联网相关处理 配网相关处理、数据上报与接收处理、云端时间获取

3.2 代码结构

tuya_air_cleaner_demo
├── platform             /* 涂鸦通用 Tuya IoTOS SDK 的开发编译环境和工具链 */
├── sdk                  /* 存放涂鸦通用 Tuya IoTOS SDK 的头文件和库文件 */
└──  app                 /* 存放涂鸦通用 Tuya IoTOS SDK 的 demo */
    ├── src              /* 源文件目录 */
    │   ├── common
    │   │   ├── tuya_device.c                     /* 应用层入口 */
    │   │   ├── tuya_dp_process.c                 /* DP上下发处理 */
    │   │   ├── tuya_iot_funtion.c                /* 连接IOT云 */
    │   │   └── tuya_key_funtion.c                /* 按键处理 */
    │   ├── driver
    │   │   ├── mfrc522
    │   │   │   ├── tuya_mfrc522_app.c            /* NFC芯片mfrc522中间层驱动 */
    │   │   │   └── tuya_mfrc522.c                /* NFC芯片mfrc522驱动 */
    │   │   ├── tm1650
    │   │   │   ├── soc_i2c.c                     /* 软件I2C模拟驱动 */
    │   │   │   ├── tm1650_app.c                  /* 液晶显示屏芯片tm1650中间层驱动 */
    │   │   │   └── tm1650.c                      /* 液晶显示屏芯片tm1650驱动 */
    │   │   ├── tuya_buz_driver.c                 /* 蜂鸣器驱动 */
    │   │   ├── tuya_hardware_driver.c            /* 硬件GPIO驱动 */
    │   │   ├── tuya_lcd_display.c                /* 液晶屏应用层驱动 */
    │   │   └── tuya_motor_driver.c               /* 风扇电机驱动 */
    │   └── function
    │       ├── tuya_air_quality_funtion.c        /* 空气质量获取和显示功能 */
    │       ├── tuya_automatic_mode_funtion.c     /* 自动模式相关功能 */
    │       ├── tuya_countdown_funtion.c          /* 倒计时功能 */
    │       ├── tuya_filter_funtion.c             /* 滤芯寿命相关功能 */
    │       ├── tuya_mode_funtion.c               /* 模式切换相关功能 */
    │       ├── tuya_nfc_funtion.c                /* 滤芯NFC检测相关功能 */
    │       └── tuya_timer_funtion.c              /* Timer相关功能 */
    └── include
        ├── common
        │   ├── tuya_device.h
        │   ├── tuya_dp_process.h
        │   ├── tuya_iot_funtion.h
        │   └── tuya_key_funtion.h
        ├── driver
        │   ├── mfrc522
        │   │   ├── tuya_mfrc522_app.h
        │   │   └── tuya_mfrc522.h
        │   ├── tm1650
        │   │   ├── soc_i2c.h
        │   │   ├── tm1650_app.h
        │   │   └── tm1650.h
        │   ├── tuya_buz_driver.h
        │   ├── tuya_hardware_driver.h
        │   ├── tuya_lcd_display.h
        │   └── tuya_motor_driver.h
        └── function
            ├── tuya_air_quality_funtion.h
            ├── tuya_automatic_mode_funtion.h
            ├── tuya_countdown_funtion.h
            ├── tuya_filter_funtion.h
            ├── tuya_mode_funtion.h
            ├── tuya_nfc_funtion.h
            └── tuya_timer_funtion.h

3.3 应用框架

下图所示为基于 Tuya Wi-Fi SDK 的应用框架:

  • Platform:所使用的芯片平台,芯片 + 协议栈由芯片公司维护。

  • Port:Tuya Wi-Fi SDK 所需要的抽象接口,需要用户根据具体的芯片平台移植实现。

  • Tuya Wi-Fi SDK : 封装了涂鸦 Wi-Fi 通信协议,提供构建涂鸦 Wi-Fi 应用所需的服务接口。

  • Application:基于Tuya Wi-Fi SDK 构建的应用。

  • Tuya SDK API:API用于设备实现Wi-Fi相关的管理、通信等,API的调用将采用基于消息的异步机制,API的执行结果将会以 Message 或者 Call back 的方式通知给设备的 Application。

  • SDK Config:Tuya Wi-Fi SDK 可裁剪可配置,通过配置文件中的宏定义可将 Tuya Wi-Fi SDK 设置成不同模式,例如配置成适用于多协议设备的通用配网模式、单模配网模式、是否使用 OS 等。

  • Main Process:为 Tuya SDK API 的主引擎,Application 需要一直调用,如果 Platform 架构是带OS的,Tuya Wi-Fi SDK 会基于 Port 层提供的OS相关接口自动创建一个任务用于执行Main Process,如果是非OS平台,需要设备 Application 循环调用。

  • Message or Call back:SDK 通过 Message 或者设备 Application注册的 Call back 函数向设备 Application 发送数据(状态、数据等)。

3.4 驱动软件模块

3.5 方案流程图

(1)模式选择功能流程图:

(2)按键功能流程图

(3)滤芯检测流程图

4. 功能实现

4.1 外设驱动

4.1.1 段码液晶屏

段码液晶屏由 3 * 8 断码屏和 5 个指示灯构成,分别对应PM2.5,PM10,TVOC,温度,湿度。

其中外挂驱动芯片 TM1650,I2C 通信,本文demo中使用 2 * GPIO 模拟 I2C 。

/** * @brief: tuya_lcd_show_num * @desc: show num on lcd * @param[in] number:-99<number<999 * @return OPERATE_RET OPRT_OK is sucess, other is fail */
OPERATE_RET tuya_lcd_show_num(IN INT_T number)
{ 
        
    OPERATE_RET op_ret = OPRT_OK;
    MINUS_FLAG_E minus_flag = MINUS_NULL;
    UCHAR_T i = 0;
    UCHAR_T bit[3];

    if (number > MAX_NUM) { 
        
        number = MAX_NUM;
    }
    if (number < MIN_NUM) { 
        
        number = MIN_NUM;
    }
    if (number < 0) { 
        
        if (number > -10) { 
          //-10<number<0
            minus_flag = MINUS_TEN;
        } else { 
                     //-100<number<-10
            minus_flag = MINUS_HUNDRED;
        }
        number = number * (-1);
    }

    bit[0] = number / 100 % 10;  //hundred
    bit[1] = number / 10 % 10;   //ten
    bit[2] = number % 10;        //one

    /* hundred == 0 */
    if (bit[0] == 0) { 
        
        if (minus_flag == MINUS_HUNDRED) { 
        
            bit[0] = SEG_SHOW_MINUS;  //hundred bit show minus
        } else { 
        
            bit[0] = SEG_SHOW_NULL;   //hundred bit show null
        }
    /* ten == 0 */
        if (bit[1] == 0) { 
        
            if (minus_flag == MINUS_TEN) { 
        
                bit[1] = SEG_SHOW_MINUS;  //ten bit show minus
            } else { 
        
                bit[1] = SEG_SHOW_NULL;  //ten bit show null
            }
        }
    }

    /* i2c send command and data */
    for (i = 0; i < 3; i++) { 
        
        op_ret = tuya_tm1650_app_write(dig[i], seg_num[bit[i]]); 
        if (op_ret != OPRT_OK) { 
        
            PR_ERR("tuya_tm1650_app_write error:%d", op_ret);
            return op_ret;
        }
    }
    
    return op_ret;
}

/** * @brief: tuya_led_show * @desc: Choose the led that led show * @param[in] show_led: 0,1,2,3,4,5 * @return OPERATE_RET OPRT_OK is sucess, other is fail */
OPERATE_RET tuya_led_show(IN CONST UCHAR_T show_led)
{ 
        
    OPERATE_RET op_ret = OPRT_OK;

    //Check the input parameter, show_led: 0~5
    if (show_led > 5) { 
        
        PR_ERR("show_led input parameter error!");
        return OPRT_INVALID_PARM;
    }
    
    /* i2c send command and data */
    op_ret = tuya_tm1650_app_write(dig[3], seg_led[show_led]); 
    if (op_ret != OPRT_OK) { 
        
        PR_ERR("tuya_tm1650_app_write error:%d", op_ret);
        return op_ret;
    }
    return op_ret;
}
4.1.2 风扇电机

采用空气净化器专用的直流无刷电机,模块主要引脚如下表。

引脚 I/O 直流电压值 说明
CLK IN VIH:4.5~5.0VIL:0.5V max CLK = 50~425 HzDuty 50%
FG OUT VIH:4.5~5.0VIL:0.5V max FG(Hz)= SPEED[r/min] * 15/60
BRK IN VIH:4.5~5.0VIL:0.5V max Hi:BREAK ON (Motor Stop)Low:BREAK OFF (Motor Start)

电源和I/O引脚输入步骤:

开机:5V on > 24V on > CLK

关机:BRK输入Hi > CLK信号输入Low > 24V off > 5V off

/** * @brief: _tuya_motor_start * @desc: start motor to control fan speed * @param[in] percent:set fan speed percent, 0.2~1 * @return OPERATE_RET OPRT_OK is sucess, other is fail */
STATIC OPERATE_RET _tuya_motor_start(IN CONST FLOAT_T percent)
{ 
        
    OPERATE_RET op_ret = OPRT_OK;
    FLOAT_T speed_percent = 0;

    speed_percent = percent;

    //Check the input parameter, percent: 0.2~1
    if (speed_percent < 0.2) { 
        
        speed_percent = 0.2;
    }
    if (speed_percent > 1) { 
        
        speed_percent = 1;
    }

    /* Break pin off */
    tuya_gpio_write(MOROR_BRK_PIN, TRUE);

    /* Start motor pwm */
    op_ret = tuya_motor_pwm_start(430 * speed_percent); //430hz is max frequency
    if (op_ret != OPRT_OK) { 
        
        PR_ERR("tuya_motor_pwm_start error:%d", op_ret);
        return op_ret;
    }

    return op_ret;
}

/** * @brief: tuya_motor_stop * @desc: stop motor * * @return OPERATE_RET OPRT_OK is sucess, other is fail */
OPERATE_RET tuya_motor_stop(VOID_T)
{ 
        
    OPERATE_RET op_ret = OPRT_OK;

    /* Break pin on */
    tuya_gpio_write(MOROR_BRK_PIN, FALSE);

    /* stop motor pwm */
    op_ret = tuya_pwm_stop(p_pwm_motor);
    if (op_ret != OPRT_OK) { 
        
        PR_ERR("stop motor pwm error:%d", op_ret);
        return op_ret;
    }

    /* set g_fan_speed_percent value*/
    g_fan_speed_percent = 0;

    PR_DEBUG("ali, tuya_motor_stop suc");
    return op_ret;
}

4.2 应用功能

4.2.1 空气指标获取

向空气质量传感器发送命令,然后读取UART数据缓存,解析得到空气质量指标。

CONST UCHAR_T uart_buf_tx[4] = { 
        0x11, 0x01, 0x16, 0xD8};

/** * @brief: tuya_get_air_quality_index * @desc: get air quality index * * @return OPERATE_RET OPRT_OK is sucess, other is fail */
STATIC OPERATE_RET tuya_get_air_quality_index(VOID_T)
{ 
        
    OPERATE_RET op_ret = OPRT_OK;
    UINT_T tmp_len = 0, read_len = 0, read_time = 0;
    UCHAR_T uart_buf_rx[UART_BUFSZ];
    UINT_T cs_check_num = 0;

    /* write command 11 01 16 D8 */
    tuya_uart_write(p_uart0, uart_buf_tx, sizeof(uart_buf_tx));

    /* read the return data */
    do { 
        
        tmp_len = tuya_uart_read(p_uart0, &uart_buf_rx[read_len], UART_BUFSZ - read_len);
        read_len += tmp_len;
        if (read_time++ > 20) { 
        
            PR_ERR("uart no return data");
            return OPRT_COM_ERROR;
        }
        tuya_hal_system_sleep(10);
    } while (read_len < UART_BUFSZ);

    /* check frame header */
    if ((uart_buf_rx[0] != 0x16) || (uart_buf_rx[1] != 0x13) || (uart_buf_rx[2] != 0x16)) { 
        
        PR_ERR("uart receive data error, frame header");
        return OPRT_COM_ERROR;
    }

    /* check cs check num */
    cs_check_num = 256 - tuya_get_check_sum(uart_buf_rx, UART_BUFSZ-1);
    if (uart_buf_rx[UART_BUFSZ-1] != cs_check_num) { 
        
        PR_ERR("uart receive data error, cs check num");
        return OPRT_COM_ERROR;
    }

    /* set air quality index value */
    g_air_quality_index.pm25 = uart_buf_rx[9]*256 + uart_buf_rx[10];
    g_air_quality_index.pm10 = uart_buf_rx[11]*256 + uart_buf_rx[12];
    g_air_quality_index.tvoc = (uart_buf_rx[3]*256 + uart_buf_rx[4]);  //ppb,alitest
    g_air_quality_index.temperature = (uart_buf_rx[13]*256 + uart_buf_rx[14] - 500)/10;
    g_air_quality_index.humidity = (uart_buf_rx[15]*256 + uart_buf_rx[16])/10;

    return op_ret;
}
4.2.2 风扇自动调节

风扇自动调节规则:

空气质量 风扇转速
115+ 污染 保持最大风速运行,对应最大占空比,同时主控监测PM2.5的变化,如果一直在 115+,则一直最高档运行。
75 - 115 微污染 80% 风速运行,同时检测PM2.5的变化,每隔 30s 检测一次PM2.5的变化数值,如果下降小于 1,则切换最大风速运行,在PM2.5到达 75 下之前,保持最大风速。
35 - 75 60% 风速运行,同时检测PM2.5的变化,30s 内数值下降小于 1,则切换为80% 风速,下个 30s 数值下降仍小于 1,则切换 100% 风速(如果大于 1,则保持 80% ),之后保持 100% 风速,直到pm2.5降到 35 以内。
0 - 35 40% 风速运行,同时检测PM2.5变化,30s 数值如果下降超过1,则维持风速运行;如果数值不降反升(超过 35 ),则切换为上述工作模式。

滤芯寿命降低导致净化效果衰减,通过风速提升进行补偿:

80-100%, 按上述规则

50-80%, 风速提升10%

20-50%,风速提升20%

0-20%,风速提升30%

/** * @brief: tuya_auto_fan_speed_start * @desc: start auto fan speed * @param[in] start: * TRUE: start * FLASE: stop * @return OPERATE_RET OPRT_OK is sucess, other is fail */
OPERATE_RET tuya_auto_fan_speed_start(IN CONST BOOL_T start)
{ 
        
    OPERATE_RET op_ret = OPRT_OK;

    if (FALSE == start) { 
        
        tuya_pm25_scan_timer_stop();
        PR_DEBUG("stop auto fan speed");
        /* release flag */
        compare_flag.pm25_75_115 = FALSE;
        compare_flag.pm25_35_75 = FALSE;
        compare_flag.pm25_35_75_again = FALSE;
        motor_already_stop = FALSE;
        return op_ret;
    }

    /* release time */
    g_pm25_good_time = 0;

    /* scan pm2.5 value once */
    PR_DEBUG("start auto fan speed");
    tuya_pm25_scan();

    /* cycle scan pm2.5, 30s */
    tuya_pm25_scan_timer_start();

    return op_ret;
}

/** * @brief: tuya_pm25_scan * @desc: scan pm2.5 value once * * @return OPERATE_RET OPRT_OK is sucess, other is fail */
OPERATE_RET tuya_pm25_scan(VOID_T)
{ 
        
    OPERATE_RET op_ret = OPRT_OK;
    PM25_VALUE_RANGE_E pm25_value_range;
    FLOAT_T speed_percent = 0;
    UINT_T pm25_new = 0;

    pm25_new = g_air_quality_index.pm25;
    g_pm25_reduction = g_pm25_last - pm25_new;
    g_pm25_last = pm25_new;

    /* Check whether to into the dark sleep mode */
    if (TRUE == stop_auto_speed_flag) { 
        
        return op_ret;
    }

    /* Check whether to into the environmental mode */
    if (pm25_new < 35) { 
        
        //hold for 2 hours, 240*30 s
        if (g_pm25_good_time++ >= 240) { 
        
            if (g_pm25_good_time > 1000) { 
        
                g_pm25_good_time = 1000;
            }
            if (FALSE == motor_already_stop) { 
        
                tuya_motor_stop();
                motor_already_stop = TRUE;
            }
            return op_ret;
        }
    } else { 
        
        g_pm25_good_time = 0;
        motor_already_stop = FALSE;
    }

    /* Check pm2.5 value range */
    if (pm25_new >= 115) { 
        
        pm25_value_range = PM25_RANGE_115;
    } else if (pm25_new >= 75) { 
        
        pm25_value_range = PM25_RANGE_75_115;
    } else if (pm25_new >= 35) { 
        
        pm25_value_range = PM25_RANGE_35_75;
    } else { 
        
        pm25_value_range = PM25_RANGE_35;
    }

    /* control fan speed */
    switch (pm25_value_range)
    { 
        
        /* pm2.5: 115+ */
        case PM25_RANGE_115:
            speed_percent = 1;
            break;
        /* pm2.5: 75-115 */
        case PM25_RANGE_75_115:
            if (TRUE == compare_flag.pm25_35_75_again) { 
        
                speed_percent = 1;
            } else { 
        
                speed_percent = 0.8;
            }

            if (TRUE == compare_flag.pm25_75_115) { 
        
                speed_percent = tuya_pm25_compare_75_115();
            } else { 
        
                compare_flag.pm25_75_115 = TRUE;
            }
            break;
        /* pm2.5: 35-75 */
        case PM25_RANGE_35_75:
            compare_flag.pm25_75_115 = FALSE;
            speed_percent = 0.6;
            if (TRUE == compare_flag.pm25_35_75) { 
        
                speed_percent = tuya_pm25_compare_35_75();
                compare_flag.pm25_35_75 = FALSE;
            } else if ((TRUE == compare_flag.pm25_35_75_again)) { 
        
                speed_percent = tuya_pm25_compare_35_75_again();
            } else { 
        
                compare_flag.pm25_35_75 = TRUE;
            }
            break;
        /* pm2.5: 0-35 */
        case PM25_RANGE_35:
            compare_flag.pm25_75_115 = FALSE;
            compare_flag.pm25_35_75_again = FA

标签: 40芯连接器带线1s10芯连接器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台