所使用的模块与工具:
- 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
)。
- 第一行:注册处理所有 Wi-Fi 事件(
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);
- 将传入的
ssid
和password
复制到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_EVENT
和IP_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); // 删除当前任务
}
软件层
- 数据收集
- 温度和湿度数据
- 可以使用API或网站(如OpenWeatherMap)获取当前天气状况的温度和湿度数据。
- 可以使用历史数据(如气象站记录)来预测天气模式。
- 地理信息数据
- 使用GIS库(如Folium或Geopandas)获取地理坐标、城市信息和气象站数据。
- 使用地图数据(如OpenStreetMap或国土测绘数据)获取城市和区域信息。
- 其它相关数据
- 可以使用API或网站获取城市人口、经济情况和其它环境因素(如空气质量、噪音指数等)。
- 使用社交媒体或用户评论数据,了解用户对所在城市的感受和需求。
- 温度和湿度数据
- 数据预处理
- 数据清理和转换
- 转换所有数据为标准格式和单位,以便进行比较和分析。
- 删除异常值或错误数据。
- 数据集成和标准化
- 集成来自各个数据源的数据,确保所有数据与所在城市的坐标和信息相关联。
- 进行标准化(如归一化或标准化),使不同特征的数据具有相同的范围和分布。
- 数据清理和转换
- 特征工程
- 提取城市环境特征
- 使用GIS功能提取城市环境特征,如面积、人口密度、交通网络、绿地面积等。
- 使用机器学习算法提取城市环境模式,如天气模式、空气指数等。
- 构建特征矩阵
- 将提取的特征构建为矩阵格式,以便进行分析和比较。
- 提取城市环境特征
- 环境模拟
- 机器学习算法
- 选择适合的机器学习算法,如决策树、随机森林、支持向量机等。
- 使用这些算法训练一个模型,根据用户所在地的环境数据预测相应城市的环境数据。
- 城市环境推断
- 使用训练好的模型,对用户所在地的环境数据进行预测,推断出与所在地环境相近的城市。
- 机器学习算法
- 结果评估和优化
- 结果评估
- 使用评估指标(如准确性、召回率、F1分数等)评估模型的性能。
- 进行错误分析,找出错误数据或模式的原因。
- 模型优化
- 根据错误分析结果和评估指标对模型进行优化和改进。
- 试验不同算法和参数,以找到最佳的模型性能。
- 结果评估
以下是一些常用的算法和库:
- 决策树和随机森林:scikit-learn(Python)
- 支持向量机:libsvm(C++)和scikit-learn(Python)
- K近邻算法:scikit-learn(Python)
- GIS库:Folium(Python)、Geopandas(Python)和GeoServer(Java)
- 地图数据:OpenStreetMap(Osmosis)和国土测绘数据