文章目录
简介: 本文方案在保持原始功能的同时改善了净化强度工作方式,增加滤芯寿命计算功能。可以自动检测空气中有害物质的浓度,根据污染浓度实时自动调节净化强度,还能检测空气净化器滤芯使用情况,计算滤芯使用寿命,提醒更换滤芯等功能。
先附上源码:
[github]: “空气净化器demo”
一. 硬件方案
1. 硬件框图
2. 特性描述
- 主控模块主控模块采用涂鸦自研的WBRU模组,用于控制整机运行,包括按键检测,LED指示,负载控制,数据通讯等工作
- 采用四合一粉尘传感器实时测量输出温湿度、VOC 浓度、PM1.0/PM2.5 /PM10质量浓度
- 蜂鸣器采用压电式无源蜂鸣器,通过模组输出相应频率PWM信号驱动
- 微动开关可以防止误打开滤芯所在后盖
- 光敏传感器可以检测环境光线强度,自动调节显示亮度
- LED可直观显示定时、异常警报等信息
- NFC读卡器可以有效检测滤芯的匹配情况与使用寿命
3. 设计方案
3.1 主控单元
本方案选用一款低功耗WBRU模组作为主控板,并基于该模组进行SoC开发;实现电机控制和传感器数据采集。
3.2 电机控制
空气净化器的风机是由选用稻津的 MI-JK48HM710,控制信号主要由PWM、FG、BRK,控制信号与模组之间通讯需进行电平转换。
3.3 按键与感光传感器
系统有两个按键,主按键: 长按开关机,短按切换不同的工作模式(自动,睡眠,最爱);屏幕按键: 短按切换屏幕和指示灯的状态(PM2.5,PM10,TVOC,温度,湿度,关), 长按进入配网模式。系统中有一个光敏二极管,用于控制夜间光线较暗时显示的亮度。
3.4 显示电路
方案使用一个TM1650驱动IC来驱动一个3位8段数码管和5个模式指示灯。数码管用于显示空气指标,通过屏幕按键控制,分别为PM2.5数据,PM10数据,TVOC数据,温度,湿度。
3.5 NFC驱动
方案使用MFRC522,它是高度集成的非接触式读写卡芯片,可根据不同主机接口实现SPI、串行UART、I2C协议通讯。方案中使用SPI通讯来实现滤芯NFC检测功能(通过NFC判断滤芯工作时间,以及是否更换新滤芯)。
3.6 四合一传感器驱动
AM1002 是一款高性价比的多参数空气质量传感器集成模组,能够实时测量输出温湿度、VOC 浓度、PM1.0/PM2.5 /PM10 质量浓度。
3.7 电源
方案使用 LOWPOWER(微源半导体)的lp6498ab6f输出5V给一些传感器供电,ASM1117由5V转3.3V给模组供电,供电电流不低于150mA。lp6498ab6f的规格如下:最大输入电压4.530V;输出电压范围0.812V。开关频率600KHZ,最大持续电流可达1.2A。
3.8 其他驱动
此外,还有蜂鸣器,UV灯,以及机械微动开关用于仓门检测。
4. 原理图与PCB
二. 整机搭建
1. 实物展示图
三. 嵌入式功能实现
1. 功能需求
**产品功能简述:**有自动、手动、睡眠三种模式,可以通过按键和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)且保持一段时间(2h)时,关闭风扇,以节省能源,如果PM2.5再次超过35,则启动风扇自动调节。 3. 自动模式—睡眠功能:当光线传感器检测到房间已经变暗,设备自动关闭灯光和屏幕。此时如果空气质量优保持一段时间时(30min),则风扇降为最低档位(20%风速)。 4. 智能杀菌功能:温度20°以上,湿度65%以上,且近4h无触发过杀菌(开启UV灯),则开启UV杀菌持续1h。 5. 滤芯NFC检测功能:检测滤芯是否存在,并读取滤芯类别和生产信息,可防盗版。 6. 滤芯寿命智能计算功能:智能计算滤芯的粉尘累计吸附量,得到滤芯剩余寿命,并根据粉尘吸附速度和设备使用频率估算滤芯剩余可用时间。 |
配网功能 | 通用Wi-Fi+BLE配网,长按配网按键重置配网。 |
2. 环境搭建
2.1 开发环境搭建
(1)开发环境搭建可以参考Wi-Fi 模组二次开发教程——1. SoC开发环境搭建。如果已经有虚拟机和乌班图的开发环境可直接跳至4.2 下载编译依赖工具处进行剩余环境搭建。
(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. 总体设计
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.0 VIL:0.5V max | CLK = 50~425 Hz Duty 50% |
FG | OUT | VIH:4.5~5.0 VIL:0.5V max | FG(Hz)= SPEED[r/min] * 15/60 |
BRK | IN | VIH:4.5~5.0 VIL: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 风扇自动调节
风扇自动调节规则:
PM2.5(ug/m3) | 空气质量 | 风扇转速 |
---|---|---|
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 = FALSE;
speed_percent = 0.4;
break;
default:
break;
}
/* start fan motor */
op_ret = tuya_motor_start(speed_percent);
if (OPRT_OK != op_ret) {
PR_ERR("tuya_motor_start error:%d", op_ret);
return op_ret;
}
return op_ret;
}
4.2.3 滤芯NFC检测
NFC读卡器芯片为 MFRC522,滤芯上的NFC卡片类型为 NXP MIFARE Ultralight,内存 180 bytes,45 页(4 bytes 每页)。
NFC读卡器的步骤:寻卡
> 防冲撞
> 选卡
> 校验密码
> 读写卡片
。Mifare Ultralight 类型卡片没有密码认证(Mifare One 类型卡片支持密码认证),所以使用 Mifare Ultralight 类型卡片的话就可以跳过校验密码
步骤。
滤芯NFC卡片信息数据协议定义如下表:
内存读写地址 | 长度(byte) | 说明 |
---|---|---|
12页 | 4 | Data[0]:标识,0x74,”t“,表示 tuya。 Data[1]:Type,滤芯的类型。0x00 表示标准,0x01 表示抗菌,0x02 表示除醛, 0x03 表示专业。 Data[2]:滤芯ID,高位 Data[3]:滤芯ID,低位 |
13页 | 4 | Data[0]:生产年份,0x00 表示 2000 年 Data[1]:生产月份,从 1 开始到 12 结束 Data[2]:生产日份,从 1 开始到 31 结束 Data[3]:保留 |
滤芯NFC检测部分代码:
/**
* @brief: tuya_nfc_detect_filter
* @desc: nfc detect to read filter message
*
* @return OPERATE_RET OPRT_OK is sucess, other is fail
*/
STATIC OPERATE_RET tuya_nfc_detect_filter(VOID_T)
{
OPERATE_RET op_ret = OPRT_OK;
UCHAR_T i = 0;
UCHAR_T page = 0;
UCHAR_T read_buf[16] = {0};
UCHAR_T TagType[2];
UCHAR_T SelectedSnr[4];
/* mfrc522 reset, and open antenna */
PcdReset();
PcdAntennaOff();
PcdAntennaOn();
/* look for card */
op_ret = PcdRequest(0x52, TagType);
if (OPRT_OK != op_ret) {
PR_INFO("PcdRequest err:%d", op_ret);
PR_INFO("Do not find NFC Card, filter do not install");
return op_ret;
}
/* printf the type of card */
PR_DEBUG_RAW("TagType: 0x");
for (i = 0; i < 2; i++) {
PR_DEBUG_RAW("%02X", TagType[i]);
}
PR_DEBUG_RAW("\n");
/* card collision protection */
op_ret = PcdAnticoll(SelectedSnr);
if (OPRT_OK != op_ret) {
PR_INFO("PcdAnticoll err:%d", op_ret);
PR_INFO("Do not find NFC Card, filter do not install");
return op_ret;
}
/* select card */
op_ret = PcdSelect(SelectedSnr);
if (OPRT_OK != op_ret) {
PR_INFO("PcdSelect err:%d", op_ret);
PR_INFO("Do not find NFC Card, filter do not install");
return op_ret;
}
/* read card */
PR_DEBUG("Find NFC Card, the filter is install ok");
PR_DEBUG_RAW("Read NFC Card data:\n");
for (page = 0; page < 45; page +=4) {
//read
op_ret = PcdRead(page, read_buf);
if (OPRT_OK != op_ret) {
PR_ERR("PcdRead err:%d, page:%d~%d", op_ret, page, (page + 3));
}
//get filter message
if (CARD_READ_PAGE_ADDR == page) {
memset(filter_buf, 0x00, CARD_READ_BYTE_LEN);
for (i = 0; i < CARD_READ_BYTE_LEN; i++) {
filter_buf[i] = *(read_buf + i);
}
}
//printf read buf
PR_DEBUG_RAW(" ");
for (i = 0; i < 16; i++) {
PR_DEBUG_RAW("%02X ", read_buf[i]);
}
PR_DEBUG_RAW("----- page: %d~%d\n", page, (page + 3));
}
PR_DEBUG_RAW("\n");
op_ret = tuya_analyze_CardData_to_get_filter_msg();
if (OPRT_OK != op_ret) {
return op_ret;
}
return op_ret;
}
/**
* @brief: tuya_analyze_CardData_to_get_filter_msg
* @desc: analyze CardData to get filter msg
*
* @return OPERATE_RET OPRT_OK is sucess, other is fail
*/
STATIC OPERATE_RET tuya_analyze_CardData_to_get_filter_msg(VOID_T)
{
OPERATE_RET op_ret = OPRT_OK;
UCHAR_T i = 0;
/* check filter_buf */
if (NULL == filter_buf) {
PR_ERR("filter_buf is error");
return OPRT_INVALID_PARM;
}
/* printf filter buf */
PR_DEBUG_RAW("filter buf data: ");
for (i = 0; i < CARD_READ_BYTE_LEN; i++) {
PR_DEBUG_RAW("%02X ", filter_buf[i]);
}
PR_DEBUG_RAW("\n");
/* check if filter is piratic */
if (CARD_READ_HEAD_TAG != filter_buf[0]) {
PR_INFO("Do not have tuya tag, filter is piratic");
return OPRT_COM_ERROR;
}
/* get filter type */
switch (filter_buf[1]) {
case FILTER_STANDARD:
g_filter_card_msg.type = FILTER_STANDARD;
PR_DEBUG_RAW("filter type: standard\n");
break;
case FILTER_ANTIBACTERIAL:
g_filter_card_msg.type = FILTER_ANTIBACTERIAL;
PR_DEBUG_RAW("filter type: antibacterial\n");
break;
case FILTER_ALDEHYDE_REMOVAL:
g_filter_card_msg.type = FILTER_ALDEHYDE_REMOVAL;
PR_DEBUG_RAW("filter type: aldehyde removal\n");
break;
case FILTER_PROFESSIONAL:
g_filter_card_msg.type = FILTER_PROFESSIONAL;
PR_DEBUG_RAW("filter type: professional\n");
break;
default:
PR_INFO("filter type buf data is erro, maybe filter is piratic");
return OPRT_COM_ERROR;
break;
}
/* get filter ID */
g_filter_card_msg.ID = filter_buf[2] * 256 + filter_buf[3];
PR_DEBUG_RAW("filter ID: %d\n", g_filter_card_msg.ID);
/* get filter product date */
g_filter_card_msg.product_date.year = filter_buf[4] + 2000;
g_filter_card_msg.product_date.month = filter_buf[5];
g_filter_card_msg.product_date.day = filter_buf[6];
PR_DEBUG_RAW("filter product date: %d-%d-%d\n", filter_buf[4]+2000, filter_buf[5], filter_buf[6]);
/* DP report */
tuya_update_single_dp(FILTER_TYPE_DPID, PROP_ENUM, g_filter_card_msg.type);
/* check whether the filter is new */
tuya_check_whether_the_filter_is_new();
return op_ret;
}
4.2.4 滤芯寿命计算
(1)滤芯剩余寿命算法:
说明:
a、根据公式,只要知道当前滤芯粉尘的吸附量和pm2.5颗粒浓度,就可以算出当前粉尘吸附量;
b、每1分钟计算一次粉尘吸附量并累加;
c、当空气净化器收到关机命令时或者每过24h,系统会将此前累加的粉尘吸附量存入flash,下次开机可以继续累加;
d、滤芯寿命根据每周粉尘累加的吸附量上报一次。
**注:**若设备发生断电情况,可能会产生0~1天的误差。
(2)滤芯剩余可用天数算法:
粉尘吸附量累加一周后,就可以得到粉尘吸附的速度,根据粉尘吸附的速度和滤芯剩余的寿命,就可以估算出滤芯剩余可用天数。
滤芯寿命计算部分代码:
/**
* @brief: tuya_filter_1minute_task
* @desc: filter 1minute task
*
* @return OPERATE_RET OPRT_OK is sucess, other is fail
*/
STATIC VOID_T tuya_filter_1minute_task(VOID_T)
{
STATIC UINT_T uv_close_time = 240;
STATIC UINT_T s_tick;
while(1)
{
UINT_T fan_speed_percent = 0;
/* check whether air cleaner power on */
if (POWER_ON == tuya_get_power_status()) {
//get current fan_speed
fan_speed_percent = tuya_get_fan_speed_percent();
//figure pm_total
g_filter_pm_total = g_filter_pm_total + (FLOAT_T)fan_speed_percent/100 * FILTER_FIGURE_CONST;
g_filter_msg.pm_total = g_filter_msg.pm_total + (FLOAT_T)fan_speed_percent/100 * FILTER_FIGURE_CONST;
//DP report, pm_total
tuya_update_single_dp(PM_TOTAL_DPID, PROP_VALUE, g_filter_msg.pm_total/1000); //ug -> mg
//DP report, runtime_total
tuya_update_single_dp(RUNTIME_TOTAL_DPID, PROP_VALUE, ++g_runtime_total); //runtime_total add
}
/* enter every 60 minutes, 1hour */
if (s_tick++ >= 60){
s_tick = 0;
tuya_filter_1hour_task();
}
/* if uv close time more than 240min, scan for uv funtion */
if (FALSE == tuya_get_uv_status) {
if (uv_close_time++ >= 240) {
tuya_uv_funtion_scan();
if (uv_close_time > 1000) {
uv_close_time = 1000;
}
}
} else {
uv_close_time = 0;
}
tuya_hal_system_sleep(FILTER_TASK_DELAY_TIME); //1min
}
}
/**
* @brief: tuya_filter_1hour_task
* @desc: filter 1hour task
*
* @return OPERATE_RET OPRT_OK is sucess, other is fail
*/
STATIC VOID_T tuya_filter_1hour_task(VOID_T)
{
POSIX_TM_S local_time;
UINT_T now_days = 0;
uni_local_time_get(&local_time);
now_days = local_time.tm_year*365 + local_time.tm_mon*30 + local_time.tm_mday;
if (g_now_days == 0) {
g_now_days = now_days;
}
/* if days pass */
if (g_now_days < now_days) {
/* figure filter_use_days */
g_filter_use_days = g_filter_use_days + (now_days - g_now_days);
/* figure air cleaner run_days */
g_run_days = g_run_days + (now_days - g_now_days);
/* if air cleaner run_days more than 7 days */
if (g_run_days >= 7) {
//figure filter_life
g_filter_msg.filter_life = (1 - (FLOAT_T)g_filter_pm_total / FILTER_MAX_PM_CAN_ABSORB) * 100;
//figure filter_days
if (g_filter_pm_total == 0) {
g_filter_msg.filter_days = 180;
} else {
g_filter_msg.filter_days = ((FILTER_MAX_PM_CAN_ABSORB * g_filter_use_days) / g_filter_pm_total) - g_filter_use_days;
}
//DP report
if (g_filter_msg.filter_days > 1000) {
g_filter_msg.filter_days = 1000;
}
tuya_update_single_dp(FILTER_DAYS_DPID, PROP_VALUE, g_filter_msg.filter_days); //filter_days
tuya_update_single_dp(FILTER_LIFE_DPID, PROP_VALUE, g_filter_msg.filter_life); //filter_life
g_run_days = 0;
}
g_now_days = now_days;
/* write save data in flash */
tuya_write_save_data_in_flash();
}
}