添加 JW01 传感器支持,包含初始化和数据读取功能,判断CO2值来判断实物状态。1000为初始阈值
This commit is contained in:
3
components/JW01/CMakeLists.txt
Normal file
3
components/JW01/CMakeLists.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
idf_component_register(SRCS "JW01.c"
|
||||||
|
INCLUDE_DIRS "include"
|
||||||
|
REQUIRES esp_driver_uart)
|
||||||
184
components/JW01/JW01.c
Normal file
184
components/JW01/JW01.c
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
#include "JW01.h"
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "driver/uart.h"
|
||||||
|
#include "esp_check.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
|
||||||
|
static const char *TAG = "JW01";
|
||||||
|
static bool s_inited = false;
|
||||||
|
static uint32_t s_parse_fail_count = 0;
|
||||||
|
|
||||||
|
static esp_err_t jw01_parse_binary_frames(const uint8_t *buf, int len, jw01_data_t *out)
|
||||||
|
{
|
||||||
|
bool found = false;
|
||||||
|
float last_co2 = 0.0f;
|
||||||
|
|
||||||
|
for (int i = 0; i + 5 < len; ++i) {
|
||||||
|
if (buf[i] != 0x2C) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t cs = (uint8_t)(buf[i] + buf[i + 1] + buf[i + 2] + buf[i + 3] + buf[i + 4]);
|
||||||
|
if (cs != buf[i + 5]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t value = ((uint16_t)buf[i + 1] << 8) | buf[i + 2];
|
||||||
|
last_co2 = (float)value;
|
||||||
|
found = true;
|
||||||
|
i += 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
return ESP_ERR_INVALID_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
out->co2 = last_co2;
|
||||||
|
out->co2_valid = true;
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void str_to_upper_inplace(char *s)
|
||||||
|
{
|
||||||
|
while (*s != '\0') {
|
||||||
|
*s = (char)toupper((unsigned char)*s);
|
||||||
|
++s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool extract_float_by_key(const char *line, const char *key, float *out)
|
||||||
|
{
|
||||||
|
char up_line[160];
|
||||||
|
char up_key[24];
|
||||||
|
|
||||||
|
strncpy(up_line, line, sizeof(up_line) - 1);
|
||||||
|
up_line[sizeof(up_line) - 1] = '\0';
|
||||||
|
strncpy(up_key, key, sizeof(up_key) - 1);
|
||||||
|
up_key[sizeof(up_key) - 1] = '\0';
|
||||||
|
|
||||||
|
str_to_upper_inplace(up_line);
|
||||||
|
str_to_upper_inplace(up_key);
|
||||||
|
|
||||||
|
char *pos = strstr(up_line, up_key);
|
||||||
|
if (pos == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (*pos != '\0' && *pos != ':' && *pos != '=') {
|
||||||
|
++pos;
|
||||||
|
}
|
||||||
|
if (*pos == '\0') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
++pos;
|
||||||
|
|
||||||
|
while (*pos != '\0' && (isspace((unsigned char)*pos) || *pos == ',')) {
|
||||||
|
++pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *endptr = NULL;
|
||||||
|
float value = strtof(pos, &endptr);
|
||||||
|
if (endptr == pos) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out = value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t jw01_parse_line(const char *line, jw01_data_t *out)
|
||||||
|
{
|
||||||
|
jw01_data_t data = {0};
|
||||||
|
|
||||||
|
data.tvoc_valid = extract_float_by_key(line, "TVOC", &data.tvoc);
|
||||||
|
if (!data.tvoc_valid) {
|
||||||
|
data.tvoc_valid = extract_float_by_key(line, "VOC", &data.tvoc);
|
||||||
|
}
|
||||||
|
|
||||||
|
data.hcho_valid = extract_float_by_key(line, "HCHO", &data.hcho);
|
||||||
|
if (!data.hcho_valid) {
|
||||||
|
data.hcho_valid = extract_float_by_key(line, "CH2O", &data.hcho);
|
||||||
|
}
|
||||||
|
|
||||||
|
data.co2_valid = extract_float_by_key(line, "CO2", &data.co2);
|
||||||
|
|
||||||
|
if (!data.tvoc_valid && !data.hcho_valid && !data.co2_valid) {
|
||||||
|
return ESP_ERR_INVALID_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out = data;
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t jw01_init(void)
|
||||||
|
{
|
||||||
|
if (s_inited) {
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uart_config_t uart_cfg = {
|
||||||
|
.baud_rate = JW01_UART_BAUDRATE,
|
||||||
|
.data_bits = UART_DATA_8_BITS,
|
||||||
|
.parity = UART_PARITY_DISABLE,
|
||||||
|
.stop_bits = UART_STOP_BITS_1,
|
||||||
|
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
|
||||||
|
.source_clk = UART_SCLK_DEFAULT,
|
||||||
|
};
|
||||||
|
|
||||||
|
ESP_RETURN_ON_ERROR(uart_driver_install((uart_port_t)JW01_UART_PORT, 1024, 0, 0, NULL, 0),
|
||||||
|
TAG, "uart driver install failed");
|
||||||
|
ESP_RETURN_ON_ERROR(uart_param_config((uart_port_t)JW01_UART_PORT, &uart_cfg),
|
||||||
|
TAG, "uart param config failed");
|
||||||
|
ESP_RETURN_ON_ERROR(uart_set_pin((uart_port_t)JW01_UART_PORT,
|
||||||
|
JW01_UART_TX_GPIO,
|
||||||
|
JW01_UART_RX_GPIO,
|
||||||
|
UART_PIN_NO_CHANGE,
|
||||||
|
UART_PIN_NO_CHANGE),
|
||||||
|
TAG, "uart set pin failed");
|
||||||
|
ESP_RETURN_ON_ERROR(uart_flush_input((uart_port_t)JW01_UART_PORT), TAG, "uart flush failed");
|
||||||
|
|
||||||
|
s_inited = true;
|
||||||
|
ESP_LOGI(TAG, "JW01 init done on UART%d TX=%d RX=%d baud=%d",
|
||||||
|
JW01_UART_PORT, JW01_UART_TX_GPIO, JW01_UART_RX_GPIO, JW01_UART_BAUDRATE);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t jw01_read(jw01_data_t *out_data, int timeout_ms)
|
||||||
|
{
|
||||||
|
ESP_RETURN_ON_FALSE(out_data != NULL, ESP_ERR_INVALID_ARG, TAG, "out_data is null");
|
||||||
|
ESP_RETURN_ON_FALSE(s_inited, ESP_ERR_INVALID_STATE, TAG, "jw01 not init");
|
||||||
|
|
||||||
|
char line[160] = {0};
|
||||||
|
int n = uart_read_bytes((uart_port_t)JW01_UART_PORT,
|
||||||
|
(uint8_t *)line,
|
||||||
|
sizeof(line) - 1,
|
||||||
|
pdMS_TO_TICKS(timeout_ms));
|
||||||
|
if (n <= 0) {
|
||||||
|
return ESP_ERR_TIMEOUT;
|
||||||
|
}
|
||||||
|
line[n] = '\0';
|
||||||
|
jw01_data_t data = {0};
|
||||||
|
esp_err_t ret = jw01_parse_binary_frames((const uint8_t *)line, n, &data);
|
||||||
|
if (ret != ESP_OK) {
|
||||||
|
ret = jw01_parse_line(line, &data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret == ESP_OK) {
|
||||||
|
*out_data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret != ESP_OK) {
|
||||||
|
s_parse_fail_count++;
|
||||||
|
if (s_parse_fail_count <= 5 || (s_parse_fail_count % 20) == 0) {
|
||||||
|
ESP_LOGW(TAG, "parse failed (%s), rx_len=%d", esp_err_to_name(ret), n);
|
||||||
|
ESP_LOG_BUFFER_HEX_LEVEL(TAG, (const uint8_t *)line, n, ESP_LOG_WARN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
40
components/JW01/include/JW01.h
Normal file
40
components/JW01/include/JW01.h
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include "esp_err.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define JW01_UART_PORT 0
|
||||||
|
#define JW01_UART_TX_GPIO 43
|
||||||
|
#define JW01_UART_RX_GPIO 44
|
||||||
|
#define JW01_UART_BAUDRATE 9600
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
float tvoc;
|
||||||
|
float hcho;
|
||||||
|
float co2;
|
||||||
|
bool tvoc_valid;
|
||||||
|
bool hcho_valid;
|
||||||
|
bool co2_valid;
|
||||||
|
} jw01_data_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化 JW01 UART 传感器(UART0, TX=43, RX=44)
|
||||||
|
*/
|
||||||
|
esp_err_t jw01_init(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 读取一帧 JW01 数据
|
||||||
|
*
|
||||||
|
* @param out_data 输出数据
|
||||||
|
* @param timeout_ms 超时时间(ms)
|
||||||
|
*/
|
||||||
|
esp_err_t jw01_read(jw01_data_t *out_data, int timeout_ms);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
idf_component_register(SRCS "main.cpp"
|
idf_component_register(SRCS "main.cpp"
|
||||||
INCLUDE_DIRS "."
|
INCLUDE_DIRS "."
|
||||||
REQUIRES nvs_flash esp_wifi sntp_time aht30 esp_event esp_system wifi-connect ui lvgl_st7789_use efuse relay_ctrl bh1750 MQ-2)
|
REQUIRES nvs_flash esp_wifi sntp_time aht30 esp_event esp_system wifi-connect ui lvgl_st7789_use efuse relay_ctrl bh1750 MQ-2 JW01)
|
||||||
|
|||||||
@@ -20,8 +20,10 @@
|
|||||||
#include "bh1750_use.h"
|
#include "bh1750_use.h"
|
||||||
#include "aht30.h"
|
#include "aht30.h"
|
||||||
#include "MQ-2.h"
|
#include "MQ-2.h"
|
||||||
|
#include "JW01.h"
|
||||||
|
|
||||||
#define TAG "MAIN"
|
#define TAG "MAIN"
|
||||||
|
#define CO2_SPOILAGE_THRESHOLD_PPM 1000.0f
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
@@ -30,6 +32,9 @@ typedef struct
|
|||||||
float temp;
|
float temp;
|
||||||
float humidity;
|
float humidity;
|
||||||
float gas_percent;
|
float gas_percent;
|
||||||
|
float tvoc;
|
||||||
|
float hcho;
|
||||||
|
float co2;
|
||||||
} env_data_t;
|
} env_data_t;
|
||||||
|
|
||||||
static env_data_t s_env_data;
|
static env_data_t s_env_data;
|
||||||
@@ -92,6 +97,7 @@ extern "C" void app_main(void)
|
|||||||
lvgl_port_lock(100 / portTICK_PERIOD_MS);
|
lvgl_port_lock(100 / portTICK_PERIOD_MS);
|
||||||
ui_init();
|
ui_init();
|
||||||
lvgl_port_unlock();
|
lvgl_port_unlock();
|
||||||
|
set_var_food_status("良好");
|
||||||
|
|
||||||
// 7. 创建 UI 任务
|
// 7. 创建 UI 任务
|
||||||
xTaskCreate(ui_task, "ui_task", 8192, NULL, 5, NULL);
|
xTaskCreate(ui_task, "ui_task", 8192, NULL, 5, NULL);
|
||||||
@@ -114,31 +120,60 @@ extern "C" void app_main(void)
|
|||||||
// MQ-2 使用 ADC(GPIO8)
|
// MQ-2 使用 ADC(GPIO8)
|
||||||
ESP_ERROR_CHECK(mq2_init());
|
ESP_ERROR_CHECK(mq2_init());
|
||||||
|
|
||||||
|
// JW01 使用 UART0(GPIO43/44)
|
||||||
|
ESP_ERROR_CHECK(jw01_init());
|
||||||
|
|
||||||
// 6. 创建传感器读取任务
|
// 6. 创建传感器读取任务
|
||||||
xTaskCreate([](void *arg)
|
xTaskCreate([](void *arg)
|
||||||
{
|
{
|
||||||
aht30_handle_t aht30 = (aht30_handle_t)arg;
|
aht30_handle_t aht30 = (aht30_handle_t)arg;
|
||||||
|
uint32_t log_cnt = 0;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
float lux = 0.0f, temp = 0.0f, hum = 0.0f, gas_percent = 0.0f;
|
float lux = 0.0f, temp = 0.0f, hum = 0.0f, gas_percent = 0.0f;
|
||||||
|
jw01_data_t jw01{};
|
||||||
|
esp_err_t bh_ret = ESP_FAIL;
|
||||||
|
esp_err_t aht_ret = ESP_FAIL;
|
||||||
|
esp_err_t mq2_ret = ESP_FAIL;
|
||||||
|
esp_err_t jw_ret = ESP_FAIL;
|
||||||
// 读取 BH1750
|
// 读取 BH1750
|
||||||
if (bh1750_user_read(&lux) == ESP_OK) {
|
bh_ret = bh1750_user_read(&lux);
|
||||||
|
if (bh_ret == ESP_OK) {
|
||||||
set_var_light_val(lux);
|
set_var_light_val(lux);
|
||||||
}
|
}
|
||||||
// 读取 AHT30
|
// 读取 AHT30
|
||||||
if (aht30_get_temperature_humidity_value(aht30, &temp, &hum) == ESP_OK) {
|
aht_ret = aht30_get_temperature_humidity_value(aht30, &temp, &hum);
|
||||||
|
if (aht_ret == ESP_OK) {
|
||||||
set_var_temp(temp);
|
set_var_temp(temp);
|
||||||
set_var_humity_val(hum);
|
set_var_humity_val(hum);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 读取 MQ-2,更新空气质量变量
|
// 读取 MQ-2,更新空气质量变量
|
||||||
if (mq2_read_percent(&gas_percent) == ESP_OK) {
|
mq2_ret = mq2_read_percent(&gas_percent);
|
||||||
|
if (mq2_ret == ESP_OK) {
|
||||||
set_var_air_quity(gas_percent);
|
set_var_air_quity(gas_percent);
|
||||||
|
|
||||||
if (s_env_data_lock) {
|
|
||||||
xSemaphoreTake(s_env_data_lock, portMAX_DELAY);
|
|
||||||
s_env_data.gas_percent = gas_percent;
|
|
||||||
xSemaphoreGive(s_env_data_lock);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 读取 JW01(TVOC/HCHO/CO2)
|
||||||
|
jw_ret = jw01_read(&jw01, 200);
|
||||||
|
if (jw_ret == ESP_OK) {
|
||||||
|
if (jw01.co2_valid) {
|
||||||
|
if (jw01.co2 >= CO2_SPOILAGE_THRESHOLD_PPM) {
|
||||||
|
set_var_food_status("变质");
|
||||||
|
} else {
|
||||||
|
set_var_food_status("良好");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 每 5 次打印一次综合状态,避免日志刷屏
|
||||||
|
if ((log_cnt++ % 10) == 0) {
|
||||||
|
ESP_LOGI(TAG,
|
||||||
|
"SENS bh=%s lux=%.1f | aht=%s t=%.1f h=%.1f | mq2=%s gas=%.1f | jw01=%s co2_valid=%d co2=%.1f",
|
||||||
|
esp_err_to_name(bh_ret), lux,
|
||||||
|
esp_err_to_name(aht_ret), temp, hum,
|
||||||
|
esp_err_to_name(mq2_ret), gas_percent,
|
||||||
|
esp_err_to_name(jw_ret), jw01.co2_valid ? 1 : 0, jw01.co2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -148,6 +183,10 @@ extern "C" void app_main(void)
|
|||||||
s_env_data.lux = lux;
|
s_env_data.lux = lux;
|
||||||
s_env_data.temp = temp;
|
s_env_data.temp = temp;
|
||||||
s_env_data.humidity = hum;
|
s_env_data.humidity = hum;
|
||||||
|
s_env_data.gas_percent = gas_percent;
|
||||||
|
if (jw01.tvoc_valid) s_env_data.tvoc = jw01.tvoc;
|
||||||
|
if (jw01.hcho_valid) s_env_data.hcho = jw01.hcho;
|
||||||
|
if (jw01.co2_valid) s_env_data.co2 = jw01.co2;
|
||||||
xSemaphoreGive(s_env_data_lock);
|
xSemaphoreGive(s_env_data_lock);
|
||||||
}
|
}
|
||||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||||
|
|||||||
Reference in New Issue
Block a user