STM32根据DHT11温湿度分析城市相似度项目总结


所使用的模块与工具:

  • STM32F103C8T6
  • DHT11温湿度传感器
  • ESP32WROOM(采用ESP-IDF)
  • EMQX Serverless(MQTT Broker)
  • TiDB Cloud(云数据库)
  • 腾讯位置服务API
  • 和风天气API

硬件层

STM32

Stm32Cubemx配置

使用引脚:

  • PA10->D17(USART1_RX连到ESP32的D17)
  • P9->D16(USART1_TX连到ESP32的D16)
  • PB10->LED(用于测试系统是否正常运作,设置为GPIO_Output,推挽输出)
  • PA0->DHT11 Do口(芯片左边标S的口,最右边有-的标志连GND,中间连VCC)

Pinout&Configuration

由于硬件层面比较简单,就不采用DMA来传输了

SystemCore配置
  • GPIO

    • PA0

      • GPIO output level: low(默认输出低电平)
      • GPIO mode : Output Push Pull(推挽输出)
      • GPIO Pull-up/Pull-down:No pull-up and pull-down(无上下拉)
      • Maximum output speed:Low(这里任意)
    • PA10

      • GPIO output level: low(默认输出低电平)
      • GPIO mode : Output Push Pull(推挽输出)
      • GPIO Pull-up/Pull-down:No pull-up and pull-down(无上下拉)
      • Maximum output speed:Low(这里任意)

      USART配置采用默认。

  • NVIC(USART1 global interrupt 使能,开启USART中断)

  • RCC

    • 高速,低速都配置外部晶振时钟
  • SYS

    • Debug:Serial Wire
    • Timebase Source:SysTick,时基源选系统滴答。
Connectivity配置

USART1

  • Mode:Asynchronous(异步)
  • Hardware Flow Control:Disable(不采用硬件流控制)
  • Parameter Settings:
    • Baud Rate:115200 Bits/s
    • Word Length: 8Bits
    • Parity: None
    • Stop Bits:1

Clock Configuration

采用系统时钟为72MHz。

Project Manager

输入项目名已经存放路径,还有IDE配置。

选择只复制必要的库以及分开生成对应的c和h文件。

然后点GENERATE CODE生成代码。

代码编写获取DHT11数据

由于DHT11A通信中需要用到微妙延迟,而HAL库没有自带的微妙延迟函数,需要自己编写,这里就借助DWT来实现微妙延迟。

void Enable_DWT(void) {//Enable_DWT在main.c初始化中调用
    CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; //enable DWT
    DWT->CYCCNT = 0; // reset period counter
    DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; // enable period counter
}

void delay_us(uint32_t us)
{
  uint32_t ticks = us * (SystemCoreClock / 1000000); // compute ticks
  uint32_t start = DWT->CYCCNT;                      // get current period counter value
  while ((DWT->CYCCNT - start) < ticks)
    ; // wait for specified period
}

初始化

上方是DHT11传输时序图,由于采用单总线通信,需要先将PA0默认置高电平,PA0口为推挽输出,代码如下。

// DHT11 数据结构体
typedef struct
{
    uint8_t humidity_int;      // 湿度整数部分
    uint8_t humidity_dec;      // 湿度小数部分
    uint8_t temperature_int;   // 温度整数部分
    uint8_t temperature_dec;   // 温度小数部分
    uint8_t check_sum;        // 校验和
} DHT11_Data_TypeDef;

/**
 * @brief  初始化DHT11
 * @param  无
 * @retval 无
 */
void DHT11_Init(void)
{
    DHT11_GPIO_OUT();
    HAL_GPIO_WritePin(DHT11_GPIO_Port, DHT11_Pin, GPIO_PIN_SET);
    HAL_Delay(1000);  // 上电等待1s
}

/**
 * @brief  配置DHT11的GPIO为输出模式
 * @param  无
 * @retval 无
 */
void DHT11_GPIO_OUT(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    
    GPIO_InitStruct.Pin = DHT11_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(DHT11_GPIO_Port, &GPIO_InitStruct);
}
/**
 * @brief  配置DHT11的GPIO为输入模式
 * @param  无
 * @retval 无
 */
void DHT11_GPIO_IN(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    
    GPIO_InitStruct.Pin = DHT11_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(DHT11_GPIO_Port, &GPIO_InitStruct);
}

通信

开始通信先将PA0口置低电平,等待2s,后再拉高,保持30us,同时将PA0口置为浮空输入模式,等待DHT11发送响应输出信号到PA0口(即将PA0口电平拉低),等待时间为100us。采用轮询的方式不断判断PA0口的电平,若为低,则将其拉高准备通信,若超时则认为通信失败。接下来就可以接受40bit的数据,前2字节为湿度,3-4字节为温度,最后一字节为校验,具体代码如下。

/**
 * @brief  读取DHT11的数据
 * @param  dht11_data: DHT11数据结构体指针
 * @retval 0: 读取成功, 1: 读取失败
 */
uint8_t DHT11_Read_Data(DHT11_Data_TypeDef *dht11_data)
{
    uint8_t i, j, temp;
    uint8_t buf[5];
    uint8_t retry = 0;
    
    // 主机发送开始信号
    DHT11_GPIO_OUT();
    HAL_GPIO_WritePin(DHT11_GPIO_Port, DHT11_Pin, GPIO_PIN_RESET);
    delay_us(20000);
    HAL_GPIO_WritePin(DHT11_GPIO_Port, DHT11_Pin, GPIO_PIN_SET);
    delay_us(30); //延迟30us
    
    // 切换为输入模式,等待DHT11响应
    DHT11_GPIO_IN();
    
    // 等待DHT11拉低(响应信号) 等待100us
    retry = 0;
    while(HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin) && retry < 100)
    {
        retry++;
        delay_us(1);
    }
    if(retry >= 100) return 1;
    
    // 等待DHT11拉高
    retry = 0;
    while(!HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin) && retry < 100)
    {
        retry++;
        delay_us(1);
    }
    if(retry >= 100) return 1;
    
    // 等待DHT11拉低(准备发送数据)
    retry = 0;
    while(HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin) && retry < 100)
    {
        retry++;
        delay_us(1);
    }
    if(retry >= 100) return 1;
    
    // 开始接收40bit数据
    for(i = 0; i < 5; i++)
    {
        temp = 0;
        for(j = 0; j < 8; j++)
        {
            // 等待50us低电平结束
            retry = 0;
            while(!HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin) && retry < 100)
            {
                retry++;
                delay_us(1);
            }
            if(retry >= 100) return 1;
            
            // 延时40us
            delay_us(40);
            
            // 判断数据位是1还是0
            temp <<= 1;
            if(HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin))
            {
                temp |= 1;
                // 等待高电平结束
                retry = 0;
                while(HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin) && retry < 100)
                {
                    retry++;
                    delay_us(1);
                }
                if(retry >= 100) return 1;
            }
        }
        buf[i] = temp;
    }
    
    // 验证校验和
    if(buf[4] == (buf[0] + buf[1] + buf[2] + buf[3]))
    {
        dht11_data->humidity_int = buf[0];
        dht11_data->humidity_dec = buf[1];
        dht11_data->temperature_int = buf[2];
        dht11_data->temperature_dec = buf[3];
        dht11_data->check_sum = buf[4];
        return 0;
    }
    
    return 1;
}

下个模块实现将DHT11获取的温湿度信息通过USART传给ESP32。

温湿度传给ESP32

首先先重写printf函数,映射到usart1,在usart.c中添加该函数即可实现映射。

int fputc(int ch, FILE *f)
{
  HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1000);
  return ch;
}

接下来在main.c中编写。

DHT11_Data_TypeDef dht11_data;

if(DHT11_Read_Data(&dht11_data) == 0)
    {
        printf("Temperature: %d.%dC, Humidity: %d.%d%%",
               dht11_data.temperature_int, dht11_data.temperature_dec,
               dht11_data.humidity_int, dht11_data.humidity_dec);
    }
    else
    {
        printf("DHT11 Read Failed!\r\n");
    }

这样要是获取到的湿度是60.1,温度是12.2的话,ESP32通过串口接受到的就是Temperature: 12.2C, Humidity: 60.1%

ESP32

ESP32采用在ubuntu 20.04系统上用esp-idf编程,在此前在ubuntu上安装对应依赖

使用命令sudo apt-get install git wget flex bison gperf python3-pip python3-venv cmake ninja-build ccache libffi-dev dfu-util libusb-1.0-0 net-tools

先拉取esp工具

git clone https://gitee.com/EspressifSystems/esp-gitee-tools.git

到esp-gitee-tools目录下执行jihu-mirror.sh set,将镜像改为极狐

然后通过git拉取esp-idf

git clone --recursive https://github.com/espressif/esp-idf.git

再在esp-gitee-tools目录执行install.sh,安装编译工具。

还需要设置esp-idf的环境变量,在esp-idf目录下执行source export.sh,就会自动设置环境变量。

若烧录idf.py flash权限不够时,直接将权限设置为最大,sudo chmod 777 /dev/ttyUSB0

WIFI模块实现

#define WIFI_SSID "wifi账号"
#define WIFI_PASS "wifi密码"
static const char *TAG = "WIFI_LINK";
static bool s_wifi_connected = false;
static esp_netif_t *s_sta_netif = NULL;

使用函数wifi_init_sta()来初始化wifi,ESP_ERROR_CHECK()来核对错误代码。

ESP_ERROR_CHECK(wifi_init_sta(WIFI_SSID, WIFI_PASS));

wifi_init_sta()具体实现:

esp_err_t wifi_init_sta(const char *ssid, const char *password)
{

    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    s_sta_netif = esp_netif_create_default_wifi_sta();

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
    ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL));

    wifi_config_t wifi_config = {
        .sta = {
            .threshold.authmode = WIFI_AUTH_WPA2_PSK,
            .pmf_cfg = {
                .capable = true,
                .required = false
            },
        },
    };
    
    strncpy((char *)wifi_config.sta.ssid, ssid, sizeof(wifi_config.sta.ssid) - 1);
    strncpy((char *)wifi_config.sta.password, password, sizeof(wifi_config.sta.password) - 1);

    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
    ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
    ESP_ERROR_CHECK(esp_wifi_start());

    ESP_LOGI(TAG, "WiFi initialization completed");
    return ESP_OK;
}

1. 函数定义

esp_err_t wifi_init_sta(const char *ssid, const char *password)
  • 这是一个函数定义,函数名为 wifi_init_sta,返回类型为 esp_err_t(表示 ESP-IDF 中的错误码类型)。
  • 函数接受两个参数:
    • const char *ssid:要连接的 Wi-Fi 网络的 SSID(账号)。
    • const char *password:要连接的 Wi-Fi 网络的密码。

2. 初始化网络接口

ESP_ERROR_CHECK(esp_netif_init());
  • 调用 esp_netif_init() 初始化 ESP32 的网络接口(TCP/IP 协议栈)。
  • ESP_ERROR_CHECK 是一个宏,用于检查函数返回值是否为 ESP_OK,如果不是,则触发错误处理。

3. 创建默认事件循环

ESP_ERROR_CHECK(esp_event_loop_create_default());
  • 调用 esp_event_loop_create_default() 创建默认的事件循环。
  • 事件循环用于处理系统中发生的各种事件(如 Wi-Fi 连接成功、断开连接等)。

4. 创建默认的 STA 模式网络接口

s_sta_netif = esp_netif_create_default_wifi_sta();
  • 调用 esp_netif_create_default_wifi_sta() 创建默认的 Wi-Fi STA(Station Mode)网络接口。
  • s_sta_netif 是一个全局变量,用于保存创建的 STA 网络接口对象。

5. 初始化 Wi-Fi 配置

wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
  • wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();:使用默认配置初始化 Wi-Fi 配置结构体。
  • esp_wifi_init(&cfg):根据配置初始化 Wi-Fi 驱动。

6. 注册事件处理函数

ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL));
  • 注册事件处理函数 event_handler,用于处理 Wi-Fi 和 IP 事件。
    • 第一行:注册处理所有 Wi-Fi 事件(WIFI_EVENT)。
    • 第二行:注册处理获取 IP 地址事件(IP_EVENT_STA_GOT_IP)。

7. 配置 Wi-Fi 连接参数

wifi_config_t wifi_config = {
    .sta = {
        .threshold.authmode = WIFI_AUTH_WPA2_PSK,
        .pmf_cfg = {
            .capable = true,
            .required = false
        },
    },
};
  • 定义 wifi_config_t 结构体变量 wifi_config,用于配置 Wi-Fi 连接参数。
  • sta 是 STA 模式的配置部分:
    • threshold.authmode = WIFI_AUTH_WPA2_PSK:设置认证模式为 WPA2-PSK(常用的 Wi-Fi 加密方式)。
    • pmf_cfg:配置 Protected Management Frames(PMF,保护管理帧)功能。
      • capable = true:设备支持 PMF。
      • required = false:不强制要求 PMF。

8. 设置 SSID 和密码

strncpy((char *)wifi_config.sta.ssid, ssid, sizeof(wifi_config.sta.ssid) - 1);
strncpy((char *)wifi_config.sta.password, password, sizeof(wifi_config.sta.password) - 1);
  • 将传入的 ssidpassword 复制到 wifi_config 结构体中。
  • strncpy 用于安全地复制字符串,避免缓冲区溢出。
  • sizeof(wifi_config.sta.ssid) - 1 确保字符串以 \0 结尾。

9. 设置 Wi-Fi 模式为 STA

ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
  • 调用 esp_wifi_set_mode(WIFI_MODE_STA) 设置 Wi-Fi 模式为 STA 模式(客户端模式)。

10. 设置 Wi-Fi 配置

ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
  • 调用 esp_wifi_set_config() 设置 STA 模式的 Wi-Fi 配置。

11. 启动 Wi-Fi

ESP_ERROR_CHECK(esp_wifi_start());
  • 调用 esp_wifi_start() 启动 Wi-Fi 模块。

若成功启动WIFI后会调用event_handler()函数。

**event_handler()**函数说明

在上方WIFI模块共注册两个事件基,WIFI_EVENTIP_EVENT事件,当对应事件发生时会调用该函数。

static void event_handler(void* arg, esp_event_base_t event_base,
                         int32_t event_id, void* event_data)
{
    if (event_base == WIFI_EVENT) {
        switch (event_id) {
            case WIFI_EVENT_STA_START:
                esp_wifi_connect();
                break;
            case WIFI_EVENT_STA_DISCONNECTED:
                s_wifi_connected = false;
                ESP_LOGI(TAG, "Disconnected from WiFi, trying to reconnect...");
                esp_wifi_connect();
                break;
            default:
                break;
        }
    } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
        ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
        ESP_LOGI(TAG, "Got IP: " IPSTR, IP2STR(&event->ip_info.ip));
        s_wifi_connected = true;
    }
}

在该函数中判断若事件基是wifi事件则监听是连接还是断开,ip事件只有判断获取ip事件。

若成功获取到ip则认为成功连接上wifi。

MQTT模块实现

// MQTT连接配置
#define MQTT_BROKER_URL "mqtts://mqtt地址:端口"
#define MQTT_CLIENT_ID "esp32_client"
#define MQTT_USERNAME "用户名"
#define MQTT_PASSWORD "密码"
#define MQTT_TOPIC "esp32/data"//发布的主题

// EMQX的根证书
static const char *MQTT_SERVER_ROOT_CERT = 
"填入证书";
esp_err_t mqtt_ali_init(void)
{
    // MQTT客户端配置
    esp_mqtt_client_config_t mqtt_cfg = {
        .broker.address.uri = MQTT_BROKER_URL,
        .credentials.client_id = MQTT_CLIENT_ID,
        .credentials.username = MQTT_USERNAME,
        .credentials.authentication.password = MQTT_PASSWORD,
        .broker.verification.certificate = MQTT_SERVER_ROOT_CERT,
    };

    // 创建MQTT客户端
    mqtt_client = esp_mqtt_client_init(&mqtt_cfg);
    if (mqtt_client == NULL) {
        ESP_LOGE(TAG, "Failed to initialize MQTT client");
        return ESP_FAIL;
    }

    // 注册MQTT事件处理函数
    ESP_ERROR_CHECK(esp_mqtt_client_register_event(mqtt_client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL));

    // 启动MQTT客户端
    ESP_ERROR_CHECK(esp_mqtt_client_start(mqtt_client));

    ESP_LOGI(TAG, "MQTT client initialized");
    return ESP_OK;
}

初始化部分和wifi大部分相同,不再赘述。

创建完成后若有响应事件会调用回调函数mqtt_event_handler()

// MQTT事件处理函数
void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
{
    esp_mqtt_event_handle_t event = event_data;
    
    switch ((esp_mqtt_event_id_t)event_id) {
        case MQTT_EVENT_CONNECTED:
            ESP_LOGI(TAG, "MQTT Connected to broker");
            mqtt_connected = true;
            break;
            
        case MQTT_EVENT_DISCONNECTED:
            ESP_LOGI(TAG, "MQTT Disconnected from broker");
            mqtt_connected = false;
            break;
            
        case MQTT_EVENT_SUBSCRIBED:
            ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);
            break;
            
        case MQTT_EVENT_UNSUBSCRIBED:
            ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);
            break;
            
        case MQTT_EVENT_PUBLISHED:
            ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);
            break;
            
        case MQTT_EVENT_DATA:
            ESP_LOGI(TAG, "MQTT_EVENT_DATA");
            printf("TOPIC=%.*s\r\n", event->topic_len, event->topic);
            printf("DATA=%.*s\r\n", event->data_len, event->data);
            break;
            
        case MQTT_EVENT_ERROR:
            ESP_LOGI(TAG, "MQTT_EVENT_ERROR");
            if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) {
                ESP_LOGI(TAG, "Last error code reported from esp-tls: 0x%x", event->error_handle->esp_tls_last_esp_err);
                ESP_LOGI(TAG, "Last tls stack error number: 0x%x", event->error_handle->esp_tls_stack_err);
                ESP_LOGI(TAG, "Last captured errno : %d (%s)",  event->error_handle->esp_transport_sock_errno,
                        strerror(event->error_handle->esp_transport_sock_errno));
            }
            break;
            
        default:
            ESP_LOGI(TAG, "Other event id:%d", event->event_id);
            break;
    }
}

UART模块

需要将波特率和数据位调成和STM32一致。

void uart_init(void)
{
    uart_config_t uart_config = {
        .baud_rate = 115200,                   // 波特率
        .data_bits = UART_DATA_8_BITS,         // 数据位8
        .parity = UART_PARITY_DISABLE,         // 无校验
        .stop_bits = UART_STOP_BITS_1,         // 停止位1
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, // 硬件流控制关闭
        .source_clk = UART_SCLK_APB,           // 时钟源
    };
    
    ESP_ERROR_CHECK(uart_param_config(UART_NUM, &uart_config));
    ESP_ERROR_CHECK(uart_set_pin(UART_NUM, UART_TX_PIN, UART_RX_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
    ESP_ERROR_CHECK(uart_driver_install(UART_NUM, BUF_SIZE * 2, 0, 0, NULL, 0));
    
    ESP_LOGI(TAG, "UART initialized successfully");
}

TCP模块

在使用WIfi模块和mqtt时需要启动tcp这一运输层协议。

void tcp_server_task(void *pvParameters)
{
    char rx_buffer[BUFFER_SIZE];  // 定义一个缓冲区,用于存储从客户端接收的数据
    int listen_sock, client_sock;  // 定义两个套接字:listen_sock用于监听连接,client_sock用于与客户端通信
    struct sockaddr_in server_addr, client_addr;  // 定义两个结构体,分别用于存储服务器和客户端的地址信息
    socklen_t addr_len = sizeof(client_addr);  // 定义客户端地址结构体的大小

    // 创建TCP套接字
    listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);  // 创建一个IPv4的TCP套接字
    if (listen_sock < 0) {  // 如果套接字创建失败
        ESP_LOGE(TAG, "Failed to create socket");  // 记录错误日志
        vTaskDelete(NULL);  // 删除当前任务
    }

    // 配置服务器地址
    server_addr.sin_family = AF_INET;  // 设置地址族为IPv4
    server_addr.sin_port = htons(TCP_PORT);  // 设置服务器端口号,htons函数将端口号从主机字节序转换为网络字节序
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);  // 设置服务器IP地址为任意地址,htonl函数将IP地址从主机字节序转换为网络字节序

    // 绑定套接字
    if (bind(listen_sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {  // 将套接字绑定到服务器地址
        ESP_LOGE(TAG, "Failed to bind socket");  // 如果绑定失败,记录错误日志
        close(listen_sock);  // 关闭套接字
        vTaskDelete(NULL);  // 删除当前任务
    }

    // 监听连接
    if (listen(listen_sock, MAX_CLIENTS) < 0) {  // 开始监听连接请求,MAX_CLIENTS为最大连接数
        ESP_LOGE(TAG, "Failed to listen");  // 如果监听失败,记录错误日志
        close(listen_sock);  // 关闭套接字
        vTaskDelete(NULL);  // 删除当前任务
    }

    ESP_LOGI(TAG, "TCP server started on port %d", TCP_PORT);  // 记录日志,表示TCP服务器已启动

    while (1) {  // 进入无限循环,持续处理客户端连接
        // 接受客户端连接
        client_sock = accept(listen_sock, (struct sockaddr *)&client_addr, &addr_len);  // 接受客户端连接请求
        if (client_sock < 0) {  // 如果接受连接失败
            ESP_LOGE(TAG, "Failed to accept connection");  // 记录错误日志
            continue;  // 继续下一次循环
        }

        ESP_LOGI(TAG, "New client connected");  // 记录日志,表示有新客户端连接

        // 处理客户端数据
        while (1) {  // 进入无限循环,持续处理客户端发送的数据
            int len = recv(client_sock, rx_buffer, sizeof(rx_buffer) - 1, 0);  // 从客户端接收数据
            if (len < 0) {  // 如果接收数据失败
                ESP_LOGE(TAG, "Error receiving data");  // 记录错误日志
                break;  // 退出内层循环
            } else if (len == 0) {  // 如果客户端断开连接
                ESP_LOGI(TAG, "Client disconnected");  // 记录日志,表示客户端已断开连接
                break;  // 退出内层循环
            }

            rx_buffer[len] = '\0';  // 在接收到的数据末尾添加字符串结束符
            ESP_LOGI(TAG, "Received: %s", rx_buffer);  // 记录日志,显示接收到的数据

            // 处理命令
            if (strstr(rx_buffer, "LED_ON") != NULL) {  // 如果接收到的数据包含"LED_ON"命令
                uart_send_data("1", 1);  // 通过UART发送"1",控制LED灯打开
                send(client_sock, "LED IS ON\n", 10, 0);  // 向客户端发送响应,表示LED灯已打开
            } else if (strstr(rx_buffer, "LED_OFF") != NULL) {  // 如果接收到的数据包含"LED_OFF"命令
                uart_send_data("0", 1);  // 通过UART发送"0",控制LED灯关闭
                send(client_sock, "LED IS OFF\n", 11, 0);  // 向客户端发送响应,表示LED灯已关闭
            } else {  // 如果接收到的命令无效
                send(client_sock, "Invalid command\n", 16, 0);  // 向客户端发送响应,表示命令无效
            }
        }

        close(client_sock);  // 关闭客户端套接字
    }

    close(listen_sock);  // 关闭监听套接字
    vTaskDelete(NULL);  // 删除当前任务
}

软件层

  1. 数据收集
    • 温度和湿度数据
      • 可以使用API或网站(如OpenWeatherMap)获取当前天气状况的温度和湿度数据。
      • 可以使用历史数据(如气象站记录)来预测天气模式。
    • 地理信息数据
      • 使用GIS库(如Folium或Geopandas)获取地理坐标、城市信息和气象站数据。
      • 使用地图数据(如OpenStreetMap或国土测绘数据)获取城市和区域信息。
    • 其它相关数据
      • 可以使用API或网站获取城市人口、经济情况和其它环境因素(如空气质量、噪音指数等)。
      • 使用社交媒体或用户评论数据,了解用户对所在城市的感受和需求。
  2. 数据预处理
    • 数据清理和转换
      • 转换所有数据为标准格式和单位,以便进行比较和分析。
      • 删除异常值或错误数据。
    • 数据集成和标准化
      • 集成来自各个数据源的数据,确保所有数据与所在城市的坐标和信息相关联。
      • 进行标准化(如归一化或标准化),使不同特征的数据具有相同的范围和分布。
  3. 特征工程
    • 提取城市环境特征
      • 使用GIS功能提取城市环境特征,如面积、人口密度、交通网络、绿地面积等。
      • 使用机器学习算法提取城市环境模式,如天气模式、空气指数等。
    • 构建特征矩阵
      • 将提取的特征构建为矩阵格式,以便进行分析和比较。
  4. 环境模拟
    • 机器学习算法
      • 选择适合的机器学习算法,如决策树、随机森林、支持向量机等。
      • 使用这些算法训练一个模型,根据用户所在地的环境数据预测相应城市的环境数据。
    • 城市环境推断
      • 使用训练好的模型,对用户所在地的环境数据进行预测,推断出与所在地环境相近的城市。
  5. 结果评估和优化
    • 结果评估
      • 使用评估指标(如准确性、召回率、F1分数等)评估模型的性能。
      • 进行错误分析,找出错误数据或模式的原因。
    • 模型优化
      • 根据错误分析结果和评估指标对模型进行优化和改进。
      • 试验不同算法和参数,以找到最佳的模型性能。

以下是一些常用的算法和库:

  • 决策树和随机森林:scikit-learn(Python)
  • 支持向量机:libsvm(C++)和scikit-learn(Python)
  • K近邻算法:scikit-learn(Python)
  • GIS库:Folium(Python)、Geopandas(Python)和GeoServer(Java)
  • 地图数据:OpenStreetMap(Osmosis)和国土测绘数据

文章作者: LsWorld
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 LsWorld !
评论
  目录