当前位置: 首页 > news >正文

ESPIDF备忘

ESP8266

环境搭建

Windows

首先确保安装好了vscode和git

在工作目录使用git 克隆这个

git clone --recursive https://github.com/espressif/ESP8266_RTOS_SDK.git

下载 集成环境和 ESP8266编译工具

旧版本的集成工具可能有问题 这里用20200601版本的

https://dl.espressif.com/dl/esp32_win32_msys2_environment_and_esp2020r2_toolchain-20200601.zip

https://dl.espressif.com/dl/xtensa-lx106-elf-gcc8_4_0-esp-2020r3-win32.zip

image-20250402180806337

克隆和解压后得到这仨文件

image-20250402153807835

将文件夹(\xtensa-lx106-elf-gcc8_4_0-esp-2020r3-win32\xtensa-lx106-elf)移到这里(\msys32\opt)

image-20250402153948141

将(ESP8266_RTOS_SDK)移到\msys32\home

image-20250402154413136

\msys32\etc\profile.d下的esp32_toolchain.sh中更改为以下

注意要用 / 代替 \

export PATH="$PATH:/opt/xtensa-lx106-elf/bin" 
export IDF_PATH=C:/Users/HZ12138/Desktop/temp/msys32/home/ESP8266_RTOS_SDK

vscode

在工作目录建立 .vscode/settings.json.vscode/c_cpp_properties.json

image-20250402202205595

.vscode/settings.json中写入以下代码

注意 path 要写自己的路径

{"terminal.integrated.profiles.windows": {"esp8266shell": {"path": "C:/Users/HZ12138/Desktop/temp/msys32/msys2_shell.cmd","args": ["-defterm","-mingw32","-no-start","-here"]}},"terminal.integrated.defaultProfile.windows": "esp8266shell"
}

.vscode/c_cpp_properties.json中写入以下代码

注意 path 要写自己的路径


{"configurations": [{"name": "Win32","includePath": ["${workspaceFolder}/**","C:/Users/HZ12138/Desktop/temp/msys32/home/ESP8266_RTOS_SDK/**","C:/Users/HZ12138/Desktop/temp/msys32/home/ESP8266_RTOS_SDK/components/freertos/port/esp8266/include"],"defines": ["_DEBUG","UNICODE","_UNICODE"],"cStandard": "c17","cppStandard": "gnu++14","intelliSenseMode": "windows-gcc-x64"}],"version": 4
}

Linux(Ubuntu)

linux下编译的非常快

这里选带图形化界面的

安装依赖

sudo apt-get install gcc git wget make libncurses-dev flex bison gperf vim python-is-python3 python3-pip

下载工具链 64位

解压后获得

wget https://dl.espressif.com/dl/xtensa-lx106-elf-gcc8_4_0-esp-2020r3-linux-amd64.tar.gz

image-20250403113856503

克隆SDK

git clone --recursive https://github.com/espressif/ESP8266_RTOS_SDK.git

加入环境变量

sudo vim ~/.bashrcexport PATH=${JAVA_HOME}/bin:$PATH:/home/hz12138/Desktop/ESP/xtensa-lx106-elf/bin/
alias esp8266='export IDF_PATH=/home/hz12138/Desktop/ESP/ESP8266_RTOS_SDK/'source ~/.bashrc

image-20250403122007250

安装环境

ESP8266_RTOS_SDK中运行命令行

输入并运行

可能有报错 一般不用管先试试测试

python -m pip install --user -r requirements.txt

测试

/ESP8266_RTOS_SDK/examples/get-started/hello_world下打开命令行

输入 esp8266 后输入make 无报错即可

image-20250403131513060

vscode

.vscode/c_cpp_properties.json中写入以下代码

注意 path 要写自己的路径

{"configurations": [{"name": "Linux","includePath": ["${workspaceFolder}/**","/home/hz12138/Desktop/ESP/ESP8266_RTOS_SDK/**","/home/hz12138/Desktop/ESP/ESP8266_RTOS_SDK/components/freertos/port/esp8266/include"],"defines": [],"compilerPath": "/bin/gcc","cStandard": "c17","cppStandard": "gnu++17","intelliSenseMode": "linux-gcc-x64"}],"version": 4
}

编译时先写入esp8266启用环境 再make等命令

环境相关

添加文件

步骤

  • 建立文件夹 components/XXX
  • 建立文件 XX.c XX.h component.mk
  • 修改 main/CMakeLists.txt
  • 导入头文件 #include "../components/my_UART/xx.h"

建立文件夹 components/XXX

注意一定是 components文件夹下的子文件夹

建立文件 XX.c XX.h component.mk

C和H文件名任意,component.mk一定要是这个名字,内部可留空

修改 main/CMakeLists.txt

示例:

set(src"wifi_ctrl_aio.c""xx/xx.c" 
)set(inc".""xx"
)idf_component_register(SRCS ${src}INCLUDE_DIRS ${inc})

添加大文件

主要用于添加html相关的文件

步骤

  • 建立文件夹和文件
  • component.mk中添加导入指令
  • 使用编译好的数组即可

建立文件夹和文件

建议与c文件放在同一个文件夹里

image-20250106113610175

写入component.mk

要在同级目录中写入,如上图中的component.mk

如下图,在www/test.html中写入

COMPONENT_EMBED_TXTFILES += www/test.html

使用数组

编译出的文件开始地址和结束地址为_binary_xx_html_start,_binary_xx_html_start,两个地址一减就是数组长度

extern const unsigned char script_start[] asm("_binary_test_html_start");
extern const unsigned char script_end[] asm("_binary_test_html_end");(size_t)script_end - (size_t)script_start;

编译烧录

设置烧录端口

终端输入

make menuconfig

选择串口烧录设置

image-20250104105348338

在第一项中输入端口号即可

image-20250104105441724

编译

全部编译

make all

仅编译变化

make

清理编译内容

make clean
烧录

编译并烧录

make flash

打开串口

make monitor

编译烧录并打开串口

make flash monitor

常见错误解决方法

标题字段太长服务器无法解释(WEB服务器)

Header felds are too long for server to interpret

解决方案:

更改标题字段最大长度即可

终端输入

make menuconfig

进入功能设置

image-20250108222034091

选择HTTP服务器设置

image-20250108222008059

更改即可

image-20250108222103752

APIs 和示例

UART

加入头文件

#include "driver/uart.h"

设置参数

配置结构体
描述名称功能
intbaud_rate波特率
uart_word_length_tdata_bits字节长度,常用UART_DATA_8_BITS
uart_parity_tparity校验,常用UART_PARITY_DISABLE
uart_stop_bits_tstop_bits停止位,常用UART_STOP_BITS_1
uart_hw_flowcontrol_tflow_ctrl流控制,不用留空
uint8_trx_flow_ctrl_thresh接收流控,不用留空
typedef struct {int baud_rate;                      /*!< UART baud rate*/uart_word_length_t data_bits;       /*!< UART byte size*/uart_parity_t parity;               /*!< UART parity mode*/uart_stop_bits_t stop_bits;         /*!< UART stop bits*/uart_hw_flowcontrol_t flow_ctrl;    /*!< UART HW flow control mode (cts/rts)*/uint8_t rx_flow_ctrl_thresh;        /*!< UART HW RTS threshold*/
} uart_config_t;
输入输出
描述名称功能
uart_port_tuart_num串口号(0,1,2…)
uart_config_t *uart_conf配置结构体
esp_err_t输出状态
esp_err_t uart_param_config(uart_port_t uart_num,  uart_config_t *uart_conf)

初始化

输入输出
描述名称功能
uart_port_tuart_num串口号(0,1,2…)
intrx_buffer_size接收缓冲区大小
inttx_buffer_size发送缓冲区大小
intqueue_size消息队列大小
QueueHandle_t *uart_queue消息队列句柄
intno_use未使用
esp_err_t输出状态
esp_err_t uart_driver_install(uart_port_t uart_num, int rx_buffer_size, int tx_buffer_size, int queue_size, QueueHandle_t *uart_queue, int no_use)

接收到来自串口数据时会发送给消息队列

启用UART

输入输出
描述名称功能
uart_port_tuart_num串口号(0,1,2…)
输出
void esp_vfs_dev_uart_use_driver(int uart_num)

发送字节

输入输出
描述名称功能
uart_port_tuart_num串口号(0,1,2…)
const char *src发送的数据
size_tsize数据大小
int输出发送的字节数
int uart_write_bytes(uart_port_t uart_num, const char *src, size_t size)

同时串口0也可使用printf 暂为发现设置的地方

注意:printf在收到\n时发送数据

读取字节

输入输出
描述名称功能
uart_port_tuart_num串口号(0,1,2…)
uint8_t *buf接收缓冲区
uint32_tlength接收数据大小
TickType_tticks_to_wait等待时间
int输出接收的字节数
int uart_read_bytes(uart_port_t uart_num, uint8_t *buf, uint32_t length, TickType_t ticks_to_wait)

UART示例

示例

QueueHandle_t uart0_queue;
void uart0_server(void *pvParameters)
{uart_event_t event;uint8_t *RX_buf = (uint8_t *)malloc(1024);uart_config_t uart_config = {.baud_rate = 115200,.data_bits = UART_DATA_8_BITS,.parity = UART_PARITY_DISABLE,.stop_bits = UART_STOP_BITS_1,};uart_port_t uart_num = UART_NUM_0;ESP_ERROR_CHECK(uart_param_config(uart_num, &uart_config));ESP_ERROR_CHECK(uart_driver_install(uart_num, 1024, 0, 100, &uart0_queue, 0));esp_vfs_dev_uart_use_driver(uart_num);while (true){if (xQueueReceive(uart0_queue, (void *)&event, (portTickType)portMAX_DELAY)){if (event.type == UART_DATA){uart_read_bytes(uart_num, RX_buf, event.size, portMAX_DELAY);// uart_write_bytes(uart_num, (const char *)RX_buf, event.size);printf("%s", RX_buf);}}}
}void app_main()
{xTaskCreate(uart0_server, "uart0_task", 2048, NULL, 12, NULL);
}

WIFI

TCP/IP适配器

void tcpip_adapter_init(void)

调用后初始化AP模式下默认IP为192.168.4.1

初始化事件循环

esp_err_t esp_event_loop_create_default()

事件循环:持续监测事件的发生

去初始化事件循环

esp_event_loop_delete_default

初始化WIFI

输入输出
描述名称功能
const wifi_init_config_t *config配置结构体
esp_err_t输出错误码
esp_err_t esp_wifi_init(const wifi_init_config_t *config)

可以使用下面的代码来使用默认初始化

wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();

去初始化wifi

esp_err_t esp_wifi_deinit(void)

注册事件处理函数

输入输出
描述名称功能
esp_event_base_tevent_base事件(wifi使用WIFI_EVENT IP_EVENT)
int32_tevent_id事件id,ESP_EVENT_ANY_ID是全部事件
esp_event_handler_tevent_handler事件处理的函数
void *event_handler_arg传递的参数,NULL即可
esp_err_t输出错误码

当出现事件时(包括设备上下线等)会调用回调函数

esp_err_t esp_event_handler_register(esp_event_base_t event_base, int32_t event_id,esp_event_handler_t event_handler, void* event_handler_arg)

回调函数示例

void wifi_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)

调用示例

 esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL);

注销事件处理函数

输入输出
描述名称功能
esp_event_base_tevent_base事件(wifi使用WIFI_EVENT IP_EVENT)
int32_tevent_id事件id,ESP_EVENT_ANY_ID是全部事件
esp_event_handler_tevent_handler事件处理的函数
esp_err_t输出错误码
esp_err_t esp_event_handler_unregister(esp_event_base_t event_base, int32_t event_id,esp_event_handler_t event_handler)

配置模式

输入输出
描述名称功能
wifi_mode_tmode模式,WIFI_MODE_STA,WIFI_MODE_AP,WIFI_MODE_APSTA
esp_err_t输出错误码
esp_err_t esp_wifi_set_mode(wifi_mode_t mode);

设置wifi

输入输出
描述名称功能
wifi_interface_tinterface设置模式,ESP_IF_WIFI_STA,ESP_IF_WIFI_AP
wifi_config_t *conf设置结构体
esp_err_t输出错误码
esp_err_t esp_wifi_set_config(wifi_interface_t interface, wifi_config_t *conf);
wifi_config_t 结构体
描述名称功能
wifi_ap_config_tapAP模式使用
wifi_sta_config_tstaSTA模式使用
wifi_ap_config_t 结构体
描述名称功能
uint8_t *ssidwifi名称
uint8_t *passwordwifi密码
uint8_tssid_len名称长度
uint8_tchannel通道
wifi_auth_mode_tauthmode加密模式,常用WIFI_AUTH_WPA_WPA2_PSK,WIFI_AUTH_OPEN
uint8_tssid_hidden是否隐藏,默认0不隐藏
uint8_tmax_connection最大连接数,最大4
uint16_tbeacon_interval通讯间隔,默认100ms
wifi_sta_config_t 结构体
描述名称功能
uint8_t *ssidWiFi名称
uint8_t *passwordwifi密码
wifi_scan_method_tscan_method扫描模式
boolbssid_set是否设置目标AP的MAC地址
uint8_t *bssid目标AP的MAC地址
uint8_tchannel使用的通道
uint16_tlisten_interval接收信标的监听间隔
wifi_sort_method_tsort_method通过 RSSI 或安全模式在列表中连接 AP
wifi_fast_scan_threshold_tthreshold仅使用验证模式比所选验证模式更安全且信号强于最小 RSSI 的 AP
wifi_pmf_config_tpmf_cfg受保护管理框架的配置
uint32_trm_enabled是否为连接启用无线电测量
uint32_tbtm_enabled连接是否启用BTM
uint32_treserved保留

一般只要设置wifi名和密码即可

开启wifi

esp_err_t esp_wifi_start(void);

停止wifi

esp_err_t esp_wifi_stop(void);

开始扫描wifi

输入输出
描述名称功能
const wifi_scan_config_t *config扫描配置,可传入NULL
boolblock是否阻塞,ture时扫描完成返回,false时立即返回
esp_err_t输出错误码

此函数要在STA或者AP+STA模式下使用

esp_err_t esp_wifi_scan_start(const wifi_scan_config_t *config, bool block);

获取扫描到的AP数量

输入输出
描述名称功能
uint16_t *number传出参数,获取的AP数量
esp_err_t输出错误码
esp_err_t esp_wifi_scan_get_ap_num(uint16_t *number);

获取扫描的wifi内容

输入输出
描述名称功能
uint16_t *number输入:最大可扫描AP数量 输出:扫描到的数量
esp_err_t输出错误码
wifi_ap_record_t 结构体
描述名称功能
uint8_tbssidAP的MAC地址
uint8_tssidAP的SSID
uint8_tprimaryAP的通道
wifi_second_chan_tsecondAP的次要通道
int8_trssiAP信号强度
int16_tfreq_offsetAP频偏
wifi_auth_mode_tauthmodeAP的加密模式
wifi_cipher_type_tpairwise_cipherAP对密码
wifi_cipher_type_tgroup_cipherAP组密码
wifi_ant_tant用于从AP接收信标的天线
uint32_tphy_11b11b模式标志
uint32_tphy_11g11g模式标志
uint32_tphy_11n11n模式标志
uint32_tphy_lr低速率标志
uint32_twpsWPS支持标志
uint32_treserved保留
wifi_country_tcountry国家信息
esp_err_t esp_wifi_scan_get_ap_records(uint16_t *number, wifi_ap_record_t *ap_records);

修改主机名

输入输出
描述名称功能
tcpip_adapter_if_ttcpip_if
const char *hostname主机名
esp_err_t输出错误码

注意要将LWIP_NETIF_HOSTNAME宏定义打开

esp_err_t tcpip_adapter_set_hostname(tcpip_adapter_if_t tcpip_if, const char *hostname)

示例

AP模式

/*** @brief AP模式下设备连接的回调函数* @param mac 设备的MAC地址* @author HZ12138* @date 2025-01-08 12:32:22*/
__WEAK void wifi_ap_STAconnect(uint64_t mac)
{
}
/*** @brief AP模式下设备断开的回调函数* @param mac 设备的MAC地址* @author HZ12138* @date 2025-01-08 12:33:02*/
__WEAK void wifi_ap_STAdisconnect(uint64_t mac)
{
}
/*** @brief  wifi事件处理函数(AP)* @param arg 参数* @param event_base 事件名* @param event_id 事件ID* @param event_data 数据* @author HZ12138* @date 2025-01-06 21:20:12*/
void wifi_ap_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
{uint64_t mac;wifi_event_ap_staconnected_t *event;// AP模式if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STACONNECTED){ // 设备连接event = (wifi_event_ap_staconnected_t *)event_data;mac = ((uint64_t)event->mac[5] << 40) | ((uint64_t)event->mac[4] << 32) | ((uint64_t)event->mac[3] << 24) | ((uint64_t)event->mac[2] << 16) | ((uint64_t)event->mac[1] << 8) | event->mac[0];wifi_ap_STAconnect(mac);}else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STADISCONNECTED){ // 设备断开event = (wifi_event_ap_staconnected_t *)event_data;mac = ((uint64_t)event->mac[5] << 40) | ((uint64_t)event->mac[4] << 32) | ((uint64_t)event->mac[3] << 24) | ((uint64_t)event->mac[2] << 16) | ((uint64_t)event->mac[1] << 8) | event->mac[0];wifi_ap_STAdisconnect(mac);}
}
/*** @brief wifi初始化为服务端* @param SSID wifi名称* @param password wifi密码* @return 错误码* @author HZ12138* @date 2025-01-06 21:21:33*/
esp_err_t wifi_AP_init(char *SSID, char *password)
{wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();wifi_config_t wifi_config = {.ap = {.ssid_len = strlen(SSID),.max_connection = 4,               // 最大连接数.authmode = WIFI_AUTH_WPA_WPA2_PSK // 加密方式}};esp_err_t err;// 写入SSID和密码strcpy((char *)wifi_config.ap.ssid, SSID);strcpy((char *)wifi_config.ap.password, password);// 如果密码为空,则设置为开放模式if (strlen(password) == 0)wifi_config.ap.authmode = WIFI_AUTH_OPEN;tcpip_adapter_init();                                                                                 // 初始化tcpip适配器MY_CHECK_ERR(esp_event_loop_create_default());                                                        // 初始化事件循环MY_CHECK_ERR(esp_wifi_init(&cfg));                                                                    // 初始化wifiMY_CHECK_ERR(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_ap_event_handler, NULL)); // 注册事件处理函数MY_CHECK_ERR(esp_wifi_set_mode(WIFI_MODE_AP));                                                        // 设置为AP模式MY_CHECK_ERR(esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_config));                                      // 设置AP模式的wifi配置MY_CHECK_ERR(esp_wifi_start());                                                                       // 开启wifireturn ERR_OK;
}
STA模式

/*** @brief  wifi事件处理函数(STA)* @param arg 参数* @param event_base 事件名* @param event_id 事件ID* @param event_data 数据* @author HZ12138* @date 2025-01-08 00:22:32*/
void wifi_STA_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
{ip_event_got_ip_t *event;uint8_t queue_data;if (event_base == WIFI_EVENT){switch (event_id){case WIFI_EVENT_STA_START: // WIFI以STA模式启动后触发此事件wifi_STA_disconnect_times = 0;esp_wifi_connect(); // 启动WIFI连接break;case WIFI_EVENT_STA_CONNECTED: // WIFI连上路由器后,触发此事件wifi_STA_disconnect_times = 0;// printf("connected to AP\n");break;case WIFI_EVENT_STA_DISCONNECTED: // WIFI从路由器断开连接后触发此事件wifi_STA_disconnect_times++;queue_data = WIFI_FAIL_BIT;xQueueOverwrite(wifi_STA_connect_status, &queue_data);vTaskDelay(5000 / portTICK_PERIOD_MS);esp_wifi_connect(); // 继续重连break;default:break;}}if (event_base == IP_EVENT) // IP相关事件{switch (event_id){case IP_EVENT_STA_GOT_IP: // 只有获取到路由器分配的IP,才认为是连上了路由器event = (ip_event_got_ip_t *)event_data;wifi_STA_disconnect_times = 0;queue_data = WIFI_CONNECTED_BIT;xQueueOverwrite(wifi_STA_connect_status, &queue_data);wifi_ip = event->ip_info.ip;// printf("got ip:%s\n", ip4addr_ntoa(&wifi_ip));break;}}
}/*** @brief wifi初始化为客户端* @param SSID wifi名称* @param password wifi密码** @return 错误码* @author HZ12138* @date 2025-01-08 00:22:32*/
esp_err_t wifi_STA_init(char *SSID, char *password, uint8_t *mac)
{// WIFI配置wifi_config_t wifi_config ={.sta ={.threshold.authmode = WIFI_AUTH_WPA2_PSK, // 加密方式.pmf_cfg ={.capable = true,.required = false},},};wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();esp_err_t err;wifi_STA_connect_status = xQueueCreate(1, sizeof(uint8_t));if (strlen(SSID) != 0){tcpip_adapter_init();                          // 用于初始化tcpip协议栈MY_CHECK_ERR(esp_event_loop_create_default()); // 创建默认系统事件调度循环MY_CHECK_ERR(esp_wifi_init(&cfg));             // 初始化WIFI// 注册事件MY_CHECK_ERR(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_STA_event_handler, NULL));MY_CHECK_ERR(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_STA_event_handler, NULL));// 如果有mac地址,设置BSSIDif (mac != NULL){wifi_config.sta.bssid_set = 1; // 设置BSSIDmemcpy(wifi_config.sta.bssid, mac, 6);}// 写入SSID和密码strcpy((char *)wifi_config.sta.ssid, SSID);strcpy((char *)wifi_config.sta.password, password);// 启动WIFIMY_CHECK_ERR(esp_wifi_set_mode(WIFI_MODE_STA));               // 设置工作模式为STAMY_CHECK_ERR(esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); // 设置wifi配置MY_CHECK_ERR(esp_wifi_start());                               // 启动WIFI}else{ // 没有SSID单启动不连接wifiMY_CHECK_ERR(esp_event_loop_create_default());  // 创建默认事件循环,该循环将处理所有的网络事件MY_CHECK_ERR(esp_wifi_init(&cfg));              // 配置 WiFi 模块MY_CHECK_ERR(esp_wifi_set_mode(WIFI_MODE_STA)); // 设置 WiFi 模式为 STA(Station 模式)MY_CHECK_ERR(esp_wifi_start());                 // 启动WiFi}return ESP_OK;
}
扫描wif

/*** @brief 扫描wifi热点* @attention 请在wifi_init_STA("","")函数后调用,即只打开不连接* @param ap_records 热点信息* @param ap_num 热点数量* @return 错误码* @author HZ12138* @date 2025-01-08 00:22:32*/
esp_err_t wifi_scan(wifi_ap_record_t *ap_records, uint16_t *ap_num)
{esp_err_t err;MY_CHECK_ERR(esp_wifi_scan_start(NULL, true));                  // 开始扫描可用的Wi-Fi热点MY_CHECK_ERR(esp_wifi_scan_get_ap_num(ap_num));                 // 获取可用 Wi-Fi 热点数量MY_CHECK_ERR(esp_wifi_scan_get_ap_records(ap_num, ap_records)); // 获取所有可用 Wi-Fi 热点信息return ESP_OK;
}
关闭wifi
/*** @brief 释放wifi资源* @return 错误码* @author HZ12138* @date 2025-01-08 00:22:32*/
esp_err_t wifi_deinit(void)
{esp_err_t err;esp_event_loop_delete_default();esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_ap_event_handler);esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_STA_event_handler);esp_event_handler_unregister(IP_EVENT, ESP_EVENT_ANY_ID, &wifi_STA_event_handler);MY_CHECK_ERR(esp_wifi_stop());MY_CHECK_ERR(esp_wifi_deinit());return ESP_OK;
}

非易失性存储器(NVS)

加载NVS

esp_err_t nvs_flash_init(void)

加载NVS,在使用NVS之前调用

打开NVS

输入输出
描述名称功能
const char*name命名空间名称
nvs_open_mode_topen_modeNVS_READONLY只读 NVS_READWRITE可读写
nvs_handle_t *out_handle句柄
esp_err_t输出错误码
esp_err_t nvs_open(const char* name, nvs_open_mode_t open_mode, nvs_handle_t *out_handle)

设置数据

输入输出
描述名称功能
nvs_handle_tc_handle句柄
const char*key
const void*value设置缓冲区
lengthsize_t数据长度
esp_err_t输出错误码
esp_err_t nvs_set_blob(nvs_handle_t c_handle, const char* key, const void* value, size_t length)

调用后需要调用nvs_commit后才能写入NVS

发布数据

输入输出
描述名称功能
nvs_handle_tc_handle句柄
esp_err_t输出错误码
esp_err_t nvs_commit(nvs_handle_t c_handle)

读取数据

输入输出
描述名称功能
nvs_handle_tc_handle句柄
const char*key
void*out_value读取缓冲区
size_t*length读取的数据大小
esp_err_t输出错误码
esp_err_t nvs_get_blob(nvs_handle_t c_handle, const char* key, void* out_value, size_t* length)

这个函数需要读取两次,第一次读取数据大小,第二次读取出数据内容

示例

    // 获取读取长度*len = 0;err = nvs_get_blob(my_handle, key, NULL, len);if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND)return err;// 读取数据err = nvs_get_blob(my_handle, key, buf, len);if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND)return err;

关闭NVS

输入输出
描述名称功能
nvs_handle_tc_handle句柄
输出
void nvs_close(nvs_handle_t handle)

卸载NVS(可选)

输入输出
描述名称功能
esp_err_t错误码
esp_err_t nvs_flash_deinit(void)

卸载NVS,在使用NVS之后调用

NVS使用示例

/*** @brief 向NVS中写入数据* @param name_space 命名空间* @param key 键* @param buf 缓冲区* @param len 写入的数据长度* @return 错误码 0成功* @author HZ12138* @date 2025-01-05 21:17:42*/
esp_err_t NVS_write_data(char *name_space, char *key, void *buf, size_t len)
{nvs_handle_t my_handle;esp_err_t err;// 打开NVSnvs_flash_init();err = nvs_open(name_space, NVS_READWRITE, &my_handle);if (err != ESP_OK)return err;// 设置数据err = nvs_set_blob(my_handle, key, buf, len);if (err != ESP_OK)return err;// 更新数据err = nvs_commit(my_handle);if (err != ESP_OK)return err;// 关闭NVSnvs_close(my_handle);nvs_flash_deinit();return ESP_OK;
}
/*** @brief NVS读取数据* @param name_space 命名空间* @param key 键* @param buf 缓冲区* @param len 读取的数据长度* @return 错误码 0成功* @author HZ12138* @date 2025-01-05 21:20:19*/
esp_err_t NVS_read_data(char *name_space, char *key, void *buf, size_t *len)
{nvs_handle_t my_handle;esp_err_t err;// 打开NVSnvs_flash_init();err = nvs_open(name_space, NVS_READWRITE, &my_handle);if (err != ESP_OK)return err;// 获取读取长度*len = 0;err = nvs_get_blob(my_handle, key, NULL, len);if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND)return err;// 读取数据err = nvs_get_blob(my_handle, key, buf, len);if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND)return err;// 关闭NVSnvs_close(my_handle);nvs_flash_deinit();return ESP_OK;
}

Web

此部分需要导入头文件

esp_netif.h,esp_http_server.h

服务器

开启HTTP服务器
输入输出
描述名称功能
httpd_handle_t *handle句柄
const httpd_config_t *config配置
输出
httpd_config_t 结构体
描述名称功能
unsigned inttask_priority运行服务器的FreeRTOS任务的优先级
size_tstack_size服务器任务允许的最大堆栈大小
uint16_tserver_portTCP端口号(主要数据流量)
uint16_tctrl_portUDP端口号(组件控制)
uint16_tmax_open_sockets最大客户端数量
uint16_tmax_uri_handlers最大uri头
uint16_tmax_resp_headers响应最大附加标头
uint16_tbacklog_conn积压连接数
boollru_purge_enable清除最后使用连接
uint16_trecv_wait_timeout接收超时(s)
uint16_tsend_wait_timeout发送超时(s)
void *global_user_ctx全局用户上下文
httpd_free_ctx_fn_tglobal_user_ctx_free_fn全局用户自由函数
void *global_transport_ctx全局传输内容
httpd_free_ctx_fn_tglobal_transport_ctx_free_fn全局传输自由函数
httpd_open_func_topen_fn自定义会话打开回调
httpd_close_func_tclose_fn自定义会话结束回调
esp_err_t httpd_start(httpd_handle_t *handle, const httpd_config_t *config)

可使用HTTPD_DEFAULT_CONFIG()初始化默认的配置

一般情况默认配置即可

设置页面
输入输出
描述名称功能
httpd_handle_thandle句柄
const httpd_uri_t *uri_handler配置
输出错误码
httpd_uri_t 结构体
描述名称功能
const char *uri路径
esp_err_t (*handler)(httpd_req_t *r)handler回调函数,收到请求时
void *user_ctx数据指针
esp_err_t httpd_register_uri_handler(httpd_handle_t handle,const httpd_uri_t *uri_handler)
设置响应类型
输入输出
描述名称功能
httpd_req_t *r请求体
const char *type类型,可选HTTPD_TYPE_JSON,HTTPD_TYPE_TEXT,HTTPD_TYPE_OCTET
输出错误码
esp_err_t httpd_resp_set_type(httpd_req_t *r, const char *type)
发送响应数据
输入输出
描述名称功能
httpd_req_t *r请求体
const char *buf发送缓冲区
ssize_tbuf_len发送数据长度
输出错误码
esp_err_t httpd_resp_send(httpd_req_t *r, const char *buf, ssize_t buf_len)
获取请求参数长度
输入输出
描述名称功能
httpd_req_t *r请求体
输出参数字符串长度
size_t httpd_req_get_url_query_len(httpd_req_t *r)
获取请求参数字符串
输入输出
描述名称功能
httpd_req_t *r请求体
char *buf缓冲区
size_tbuf_len缓冲区大小
输出错误码
esp_err_t httpd_req_get_url_query_str(httpd_req_t *r, char *buf, size_t buf_len)
查找请求参数
输入输出
描述名称功能
const char *qry_str参数字符串
const char *key
char *val值缓冲区
size_tval_size缓冲区大小
输出错误码
esp_err_t httpd_query_key_value(const char *qry_str, const char *key, char *val, size_t val_size)
关闭HTTP服务器
输入输出
描述名称功能
httpd_handle_t *handle句柄
输出
esp_err_t httpd_stop(httpd_handle_t handle)
示例

泛地址响应服务器

ESP8266的HTTP服务器不支持通配符

因此使用tcp套接字建立了一个HTTP服务器

步骤:

  1. 建立套接字
  2. 收到信息时建立新线程
  3. 解析HTTP数据

代码


/*** @brief 向套接字中写入数据(任意路径服务器用)* @param fd 套接字编号* @param buffer 缓冲区* @param length 写入长度* @return 错误码* @author HZ12138* @date 2025-01-10 17:17:09*/
int web_any_path_server_write(int fd, void *buffer, int length)
{int bytes_left;int written_bytes;char *ptr;ptr = (char *)buffer;bytes_left = length;while (bytes_left > 0){written_bytes = lwip_send(fd, ptr, bytes_left, 0);if (written_bytes <= 0){if (errno == EINTR)written_bytes = 0;elsereturn (-1);}bytes_left -= written_bytes;ptr += written_bytes;vTaskDelay(10);}return (0);
}
/*** @brief 任意路径web服务器GET回调* @param mwrite 写入套接字函数* @param socket_num 套接字编号* @param uri uri字符串* @param body 请求体字符串* @return 错误码* @author HZ12138* @date 2025-01-10 22:08:46*/
__WEAK int web_any_path_server_GET_callback(int (*mwrite)(int fd, void *buffer, int length), int socket_num, char *uri, char *body)
{char buf[300];// recv(socket_num, buf, 299, 0);// printf("%s\n", buf);sprintf(buf, HTTP_200, (int)(index_html_end - index_html_start) - 1);mwrite(socket_num, (void *)buf, strlen(buf));mwrite(socket_num, (void *)index_html_start, index_html_end - index_html_start - 1);return 0;
}/*** @brief 任意路径web服务器POST回调* @param mwrite 写入套接字函数* @param socket_num 套接字编号* @param uri uri字符串* @param body 请求体字符串* @return 错误码* @author HZ12138* @author HZ12138* @date 2025-01-10 22:27:14*/
__WEAK int web_any_path_server_POST_callback(int (*mwrite)(int fd, void *buffer, int length), int socket_num, char *uri, char *body)
{return 0;
}/*** @brief 任意路径web服务器处理函数* @param pfd 套接字编号* @author HZ12138* @date 2025-01-10 21:53:29*/
void web_any_path_server_request(void *pfd)
{char buf[2048] = {0}; // 数据缓冲器int socket_num = *(int *)pfd;int bytes_recvd = 0;char uri[300] = {0};char *temp = NULL;char *temp2 = NULL;char *body = NULL;char method[10] = {0};vTaskDelay(30);bytes_recvd = lwip_recv(socket_num, buf, 2048 - 1, 0);if (bytes_recvd <= 0) // 读取数据失败goto requst_error;temp = strstr(buf, "HTTP"); // 寻找HTTP标志if (temp == NULL)           // 未找到HTTPgoto requst_error;// 获取方法名 解析请求的第一个空格temp = NULL;temp = strstr(buf, " ");strncpy(method, buf, temp - buf);// printf("method:%s\n", method);// 获取uri 解析第一二个空格间内容 temp现在指向第一个空格位置temp2 = strstr(++temp, " ");strncpy(uri, temp, temp2 - temp);// printf("uri:%s\n", uri);// 获取body 解析/r/n/r/n处body = strstr(buf, "\r\n\r\n");body += 4;// printf("body:%s\n", body);if (strcmp(method, "GET") == 0) // 响应GET请求{if (web_any_path_server_GET_callback(web_any_path_server_write, socket_num, uri, body) < 0)goto requst_error;}else if (strcmp(method, "POST") == 0) // 响应POST请求{if (web_any_path_server_POST_callback(web_any_path_server_write, socket_num, uri, body) < 0)goto requst_error;}else // 其他请求不响应{if (web_any_path_server_write(socket_num, (void *)HTTP_400, strlen(HTTP_400)) < 0)goto requst_error;}vTaskDelay(30);requst_error:lwip_close(socket_num);vTaskDelete(NULL);
}/*** @brief 任意路径web服务器任务* @author HZ12138* @date 2025-01-10 16:42:02*/
void web_any_path_server_task(void *pvParameters)
{int sockfd, new_fd;struct sockaddr_in my_addr;    // 本方地址信息struct sockaddr_in their_addr; // 对方地址信息socklen_t sin_size;struct timeval tv; // 发送接收超时时间tv.tv_sec = 10;tv.tv_usec = 0;sin_size = sizeof(struct sockaddr_in);sockfd = lwip_socket(AF_INET, SOCK_STREAM, 0); // 建立socketif (sockfd == -1) // 套接字建立失败goto web_err;my_addr.sin_family = AF_INET;                     // 该属性表示接收本机或其他机器传输my_addr.sin_port = lwip_htons(80);                // 端口my_addr.sin_addr.s_addr = lwip_htonl(INADDR_ANY); // 本机IPbzero(&(my_addr.sin_zero), 8);                    // 将其他置0if (lwip_bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) < 0) // 绑定套接字数据goto web_err;lwip_listen(sockfd, 8); // 开启监听 ,第二个参数是最大监听数while (1){new_fd = lwip_accept(sockfd, (struct sockaddr *)&their_addr, &sin_size); // 在这里阻塞直到接收到消息if (new_fd == -1){ // 接收信息失败}else{// 多线程写法lwip_setsockopt(new_fd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof(tv));lwip_setsockopt(new_fd, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof(tv));int *para_fd = malloc(sizeof(int));*para_fd = new_fd; // 编号xTaskCreate(&web_any_path_server_request, "socket_task", 1024 * 4, para_fd, 6, NULL); // 建立任务}vTaskDelay(10);}web_err:vTaskDelete(NULL);
}/*** @brief web任意路径解析启动函数* @author HZ12138* @date 2025-01-10 16:38:55*/
void web_start_any_path_server(void)
{xTaskCreate(&web_any_path_server_task, "webserver_task", 2048, NULL, 5, NULL);
}

客户端

用套接字写的HTTP客户端

/*** @brief 解析域名获取地址信息* @param url_server 域名地址* @param port 端口* @return 地址信息* @author HZ12138* @date 2025-01-09 22:46:48*/
struct addrinfo *DNS_get_addrinfo(const char *url_server, const char *port)
{const struct addrinfo hints = {.ai_family = AF_INET,.ai_socktype = SOCK_STREAM,};struct addrinfo *res;int err;if (port == NULL)err = getaddrinfo(url_server, "80", &hints, &res);elseerr = getaddrinfo(url_server, port, &hints, &res);if (err != 0 || res == NULL){return NULL;}return res;
}
/*** @brief http读取函数示例* @param socket_num 套接字编号* @author HZ12138* @date 2025-01-09 22:34:03*/
void read_test(int socket_num)
{int read_num;char recv_buf[100];do{memset(recv_buf, 0, 100);read_num = lwip_read(socket_num, recv_buf, sizeof(recv_buf) - 1);uart_write_bytes(UART_NUM_0, recv_buf, read_num);} while (read_num > 0);
}/*** @brief 发送http请求* @param url_server 服务器根域名如(example.com)* @param url 请求地址(http://example.com/)* @param method 方法,大写(GET,POST)* @param body 请求体* @param read_after 读取处理函数,内部调用 lwip_read(socket_num, recv_buf, sizeof(recv_buf) - 1)来读取* @return 错误码* @author HZ12138* @date 2025-01-09 22:34:37*/
esp_err_t web_http_request(const char *url_server, const char *url, const char *method, const char *body, void (*read_after)(int socket_num))
{struct addrinfo *res;int socket_num;char *REQUEST = malloc(200 + strlen(url_server) + strlen(url) + strlen(body));sprintf(REQUEST,"%s %s HTTP/1.0\r\n""Host: %s\r\n""User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:53.0) Gecko/20100101 Firefox/53.0\r\n""Content-Length: %d\r\n""\r\n""%s",method, url, url_server, strlen(body), body);res = DNS_get_addrinfo(url_server, NULL); // 解析域名// 建立套接字socket_num = lwip_socket(res->ai_family, res->ai_socktype, 0);if (socket_num < 0){freeaddrinfo(res);return ESP_FAIL;}// 套接字连接if (lwip_connect(socket_num, res->ai_addr, res->ai_addrlen) != 0){lwip_close(socket_num);freeaddrinfo(res);return ESP_FAIL;}// 写入请求头if (lwip_write(socket_num, REQUEST, strlen(REQUEST)) < 0){lwip_close(socket_num);return ESP_FAIL;}free(REQUEST);freeaddrinfo(res);// 设置超时时间struct timeval receiving_timeout;receiving_timeout.tv_sec = 5;receiving_timeout.tv_usec = 0;if (lwip_setsockopt(socket_num, SOL_SOCKET, SO_RCVTIMEO, &receiving_timeout, sizeof(receiving_timeout)) < 0){lwip_close(socket_num);return ESP_FAIL;}// bzero(recv_buf, buf_len);// r = read(s, recv_buf, buf_len - 1);read_after(socket_num); // 读取数据函数根据情况写 使用此函数读 read(s, recv_buf, buf_len - 1);lwip_close(socket_num);return ESP_OK;
}

套接字

此部分API来自lwip

建立套接字(socket)

输入输出
描述名称功能
intdomain协议族AF_INET(IPV4),AF_INET6(IPV6)
inttype类型,SOCK_STREAM(TCP),SOCK_DGRAM(UDP)
intprotocol协议,一般写IPPROTO_IP即可,自动寻找
int输出错误-1,成功返回套接字编号
int lwip_socket(int domain, int type, int protocol)
int sock = -1;sock = lwip_socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);//UDP
sock = lwip_socket(AF_INET, SOCK_STREAM, IPPROTO_IP);//TCP

绑定套接字(设置参数)(bind)

输入输出
描述名称功能
ints套接字编号
const struct sockaddr *name设置结构体,sockaddr_in(IPV4),sockaddr_in6(IPV6)强转而来
socklen_tnamelen设置结构体长度
int输出0成功,-1失败
sockaddr_in 结构体
描述名称功能
u8_tsin_len(未知,写0即可)
sa_family_tsin_family协议族AF_INET(IPV4),AF_INET6(IPV6)
in_port_tsin_port端口(注意使用lwip_htons来换序)
in_addrsin_addrIP地址,sin_addr.s_addr存放(注意使用lwip_htonl来换序)
charsin_zero[SIN_ZERO_LEN]不使用

IPV6未用到过,用时再说

int lwip_bind(int s, const struct sockaddr *name, socklen_t namelen)

示例

struct sockaddr_in saddr = {0};
int err = 0;saddr.sin_family = PF_INET;
saddr.sin_port = lwip_htons(port);
saddr.sin_addr.s_addr = lwip_htonl(INADDR_ANY);
err = lwip_bind(sock, (struct sockaddr *)&saddr, sizeof(struct sockaddr_in));

字节转换转换函数

主机字节顺序和网络字节顺序之间转化

lwip中网络相关的使用的都是网络字节顺序

函数名输入输出功能
htonluint32_t主机到网络(32位整数)
htonsuint16_t主机到网络(16位整数)
ntohluint32_t网络到主机(32位整数)
ntohsuint16_t网络到主机(16位整数)

关闭套接字(close)

输入输出
描述名称功能
ints套接字编号
int输出0成功,-1失败
int lwip_close(int s)

读套接字

接收数据(TCP常用)(read)
输入输出
描述名称功能
ints套接字编号
void *mem读取缓冲区
size_tlen缓冲区长度
ssize_t输出>0接收的字节,=0无数据,<0错误码

此函数为阻塞接收

ssize_t lwip_read(int s, void *mem, size_t len)
获取发送者信息接收(UDP常用)(recvfrom)

UDP面向无连接,当需要知道发送方时使用此函数

注意:lwip_read的实现也来源于此函数,不过TCP连接本身可以知道数据的发送者,因此一般不需要在接收函数处获取发送者信息

输入输出
描述名称功能
ints套接字编号
void *mem读取缓冲区
size_tlen缓冲区长度
intflags标志,一般写0阻塞接收即可
struct sockaddr *from输出,发送者信息sockaddr_in(IPV4),sockaddr_in6(IPV6)强转而来
socklen_t *fromlen前项长度
ssize_t输出>0接收的字节,=0无数据,<0错误码
ssize_t lwip_recvfrom(int s, void *mem, size_t len, int flags,struct sockaddr *from, socklen_t *fromlen)

写套接字

发送数据(TCP常用)(write)
输入输出
描述名称功能
ints套接字编号
void *mem发送缓冲区
size_tlen缓冲区长度
ssize_t输出<0失败,>0写入的字节,=0读取到文件的结束
ssize_t lwip_write(int s, const void *data, size_t size)
指定发送者发送(UDP常用)(sendto)
输入输出
描述名称功能
ints套接字编号
void *mem读取缓冲区
size_tlen缓冲区长度
intflags标志,一般写0即可
struct sockaddr *to接收者信息sockaddr_in(IPV4),sockaddr_in6(IPV6)强转而来
socklen_t *tolen前项长度
ssize_t输出<0失败,>0写入的字节,=0读取到文件的结束
ssize_t lwip_sendto(int s, const void *data, size_t size, int flags,const struct sockaddr *to, socklen_t tolen)

监听套接字(listen)

输入输出
描述名称功能
ints套接字编号
intbacklog排队建立3次握手队列和刚刚建立3次握手队列的链接数和
int输出0成功,其他失败
int lwip_listen(int s, int backlog)

接受套接字(accept)

输入输出
描述名称功能
ints套接字编号
struct sockaddr *to接收者信息sockaddr_in(IPV4),sockaddr_in6(IPV6)强转而来
socklen_t *tolen前项长度
ssize_t输出-1失败,成功返回套接字编号
int lwip_accept(int s, struct sockaddr *addr, socklen_t *addrlen)

设置套接字额外参数(setsockopt)

输入输出
描述名称功能
ints套接字编号
intlevel协议层,套接字用SOL_SOCKET
intoptname选项名,见下表
const void *optval选项值缓冲区
socklen_toptlen选项值长度
int输出0成功,<0失败
int lwip_setsockopt(int s, int level, int optname, const void *optval, socklen_t optlen)

获取套接字额外参数(getsockopt)

输入输出
描述名称功能
ints套接字编号
intlevel协议层,套接字用SOL_SOCKET
intoptname选项名,见下表
const void *optval选项值缓冲区
socklen_toptlen选项值长度
int输出0成功,<0失败
int lwip_getsockopt(int s, int level, int optname, void *optval, socklen_t *optlen)

额外参数表

不全,我用到时更新

参数名功能类型读写
SO_RCVTIMEO接收数据的超时时间struct timeval可读写
SO_SNDTIMEO发送数据的超时时间struct timeval可读写

TCP连接(客户端)

步骤

  1. 建立套接字(lwip_socket)
  2. 设置远端IP和端口结构体(struct sockaddr_in)
  3. 连接服务器(lwip_connect)
  4. 操作数据(lwip_write,lwip_read)
  5. 关闭套接字(可选)

设计一个变量来存储tcp客户端列表

可以建立多个tcp连接

int tcp_client_socket[TCP_Client_MAX_NUM] = {0};/*** @brief 创建TCP套接字* @param ip 服务器IP地址* @param port 服务器端口* @return 套接字编号 -1为创建失败* @author HZ12138* @date 2025-01-13 23:31:32*/
int tcp_socket_create(ip4_addr_t ip, uint16_t port)
{struct sockaddr_in saddr = {0};int sock = -1;int err = 0;// 设置服务器地址saddr.sin_family = AF_INET;saddr.sin_port = lwip_htons(port);saddr.sin_addr.s_addr = ip.addr;sock = lwip_socket(AF_INET, SOCK_STREAM, IPPROTO_IP); // 建立socketif (sock < 0)return -1;err = lwip_connect(sock, (struct sockaddr *)&saddr, sizeof(saddr)); // 连接服务器if (err < 0){lwip_close(sock);return -1;}return sock;
}/*** @brief 发送数据* @param from 调用自 F_TCP_client* @param num 客户端编号* @param buf 数据* @param buf_len 数据长度* @return 发送数据长度 -1为发送失败* @author HZ12138* @date 2025-01-13 23:15:07*/
int tcp_client_send_data(uint8_t from, uint8_t num, uint8_t *buf, int buf_len)
{if (tcp_client_socket[num] <= 0)return -1;return lwip_write(tcp_client_socket[num], buf, buf_len);
}
/*** @brief TCP读取回调* @param from 调用自 F_TCP_client* @param num 客户端编号* @param buf 数据* @param buf_len 数据长度* @author HZ12138* @date 2025-01-13 23:18:05*/
__WEAK void tcp_read_callback(uint8_t from, int num, uint8_t *buf, int buf_len)
{printf("tcp_%d:\n", num);uart_write_bytes(UART_NUM_0, (const char *)buf, buf_len);printf("\n");tcp_client_send_data(F_TCP_client, num, buf, buf_len);
}
/*** @brief TCP客户端失败回调* @param num 客户端编号* @author HZ12138* @date 2025-01-13 23:49:13*/
__WEAK void tcp_client_fail_callback(uint8_t num)
{printf("tcp_%d close\n", num);
}
/*** @brief TCP客户端任务* @param pvParameters 客户端编号* @author HZ12138* @date 2025-01-13 23:14:52*/
void tcp_client_task(void *pvParameters)
{int num = (int)pvParameters;int buf_len;uint8_t buf[1024];while (1){buf_len = lwip_read(tcp_client_socket[num], buf, sizeof(buf));if (buf_len > 0)tcp_read_callback(F_TCP_client, num, buf, buf_len);else if (buf_len < 0){if (tcp_client_socket[num] > 0)lwip_close(tcp_client_socket[num]);tcp_client_fail_callback(num);break;}vTaskDelay(pdMS_TO_TICKS(10));}vTaskDelete(NULL);
}
/*** @brief 创建TCP客户端* @param num 客户端编号* @param ip_1 服务器IP地址* @param ip_2 服务器IP地址* @param ip_3 服务器IP地址* @param ip_4 服务器IP地址* @param port 服务器端口* @return 错误码* @author HZ12138* @date 2025-01-13 23:05:31*/
int tcp_client_open(uint8_t num, uint8_t ip_1, uint8_t ip_2, uint8_t ip_3, uint8_t ip_4, uint16_t port)
{ip4_addr_t ip;int socket_num;IP4_ADDR(&ip, ip_1, ip_2, ip_3, ip_4);socket_num = tcp_socket_create(ip, port);tcp_client_socket[num] = socket_num;if (socket_num <= 0)return -1;xTaskCreate(tcp_client_task, "tcp_client_task", 1024 * 2, (void *)num, 2, NULL);return 0;
}/*** @brief 关闭TCP客户端* @param num 客户端编号* @author HZ12138* @date 2025-01-14 10:33:20*/
void tcp_client_close(uint8_t num)
{if (tcp_client_socket[num] > 0)lwip_close(tcp_client_socket[num]);tcp_client_socket[num] = 0;
}

TCP连接(服务器)

步骤

  1. 建立服务器套接字 (lwip_socket)
  2. 设置本地的IP和端口( lwip_bind)
  3. 开始监听 (lwip_listen)
  4. 建立连接套接字( lwip_accept)
  5. 操作数据 (lwip_read,lwip_write)
/*** @brief TCP服务器读取回调* @param num 服务器编号* @param sock 套接字* @param buf 数据* @param buf_len 数据长度* @author HZ12138* @date 2025-01-14 16:18:12*/
__WEAK void tcp_server_read_callback(int num, int sock, uint8_t *buf, int buf_len)
{printf("tcp_server_%d_%d:\n", num, sock);uart_write_bytes(UART_NUM_0, (const char *)buf, buf_len);printf("\n");lwip_write(sock, buf, buf_len);
}
/*** @brief TCP服务器关闭回调* @param num 服务器编号* @author HZ12138* @date 2025-01-14 13:53:29*/
__WEAK void tcp_server_fail_callback(uint8_t num)
{printf("tcp_server%d close\n", num);
}/*** @brief TCP服务器任务* @param pvParameters 数据 [0]服务器编号 [1]套接字 [2]连接编号* @author HZ12138* @date 2025-01-14 16:15:04*/
void tcp_server_task(void *pvParameters)
{int num = ((int *)pvParameters)[0];int sock = ((int *)pvParameters)[1];int i = ((int *)pvParameters)[2];int buf_len;uint8_t buf[1024];while (1){buf_len = lwip_read(sock, buf, sizeof(buf)); // 读取数据if (buf_len > 0) // 读取到数据时回调tcp_server_read_callback(num, sock, buf, buf_len);if (buf_len < 0) // 客户端错误时关闭break;if (tcp_server_socket[num] <= 0) // 关闭服务器时关闭客户端break;vTaskDelay(pdMS_TO_TICKS(10));}// 关闭连接tcp_server_connect_socket[num][i] = -1;lwip_close(sock);vTaskDelete(NULL);
}
/*** @brief 多线程建立TCP连接* @param pvParameters 服务器编号* @author HZ12138* @date 2025-01-14 13:20:43*/
void tcp_server_create_link_tesk(void *pvParameters)
{int num = (int)pvParameters;int sock = tcp_server_socket[num];int remote_sock;int data[3];struct sockaddr_in client_addr;socklen_t client_addr_len = sizeof(client_addr);while (1){if (tcp_server_socket[num] <= 0) // 检查服务器是否关闭break;// 建立连接remote_sock = lwip_accept(sock, (struct sockaddr *)&client_addr, &client_addr_len);if (remote_sock > 0)for (int i = 0; i < TCP_Server_MAX_CONNECT_NUM; i++){if (tcp_server_connect_socket[num][i] <= 0) // 寻找空闲连接{// 写入参数tcp_server_connect_socket[num][i] = remote_sock;data[0] = num;data[1] = remote_sock;data[2] = i;xTaskCreate(tcp_server_task, "tcp_server_task", 1024 * 2, (void *)data, 1, NULL);break;}}elsebreak;vTaskDelay(pdMS_TO_TICKS(10));}lwip_close(sock);tcp_server_fail_callback(num);vTaskDelete(NULL);
}/*** @brief TCP服务器初始化* @param num 服务器编号* @param port 端口* @return  错误码* @author HZ12138* @date 2025-01-14 16:15:46*/
int tcp_server_init(uint8_t num, uint16_t port)
{int socket_num = -1;struct sockaddr_in saddr = {0};err_t err;if (num >= TCP_Server_MAX_NUM)return -1;if (tcp_server_socket[num] > 0)return -1;// 创建套接字socket_num = lwip_socket(AF_INET, SOCK_STREAM, IPPROTO_IP);if (socket_num < 0)return -1;// 绑定端口saddr.sin_family = AF_INET;saddr.sin_port = lwip_htons(port);saddr.sin_addr.s_addr = lwip_htonl(INADDR_ANY);err = lwip_bind(socket_num, (struct sockaddr *)&saddr, sizeof(saddr));if (err < 0)goto err;// 监听端口err = lwip_listen(socket_num, TCP_Server_MAX_CONNECT_NUM);if (err < 0)goto err;// 创建建立连接任务tcp_server_socket[num] = socket_num;xTaskCreate(tcp_server_create_link_tesk, "tcp_server_create_link_tesk", 1024 * 2, (void *)num, 2, NULL);return 0;err:lwip_close(socket_num);return -1;
}/*** @brief TCP服务器广播* @param num 服务器编号* @param buf 数据* @param buf_len 数据长度* @return 发送客户端数量 -1为发送失败* @author HZ12138* @date 2025-01-14 16:52:24*/
int tcp_server_Broadcast(int num, uint8_t *buf, int buf_len)
{int send_len = 0;for (int i = 0; i < TCP_Server_MAX_CONNECT_NUM; i++){if (tcp_server_connect_socket[num][i] > 0){send_len++;if (lwip_write(tcp_server_connect_socket[num][i], buf, buf_len) < 0)return -1;}}return send_len;
}
/*** @brief 关闭TCP服务器* @param num 服务器编号* @author HZ12138* @date 2025-01-14 22:44:17*/
void tcp_server_close(uint8_t num)
{int socket_num;socket_num = tcp_server_socket[num];if (socket_num > 0) // 在关闭前判断是否已经关闭lwip_close(socket_num);tcp_server_socket[num] = 0;
}

UDP发送

步骤

  1. 建立套接字 (lwip_socket)
  2. 发送数据 (lwip_sendto)
/*** @brief UDP发送数据* @param ip 目标IP* @param port 目标端口* @param buf 数据* @param buf_len 数据长度* @return 发送数据长度 -1为发送失败* @author HZ12138* @date 2025-01-14 22:37:35*/
int udp_send_data(ip4_addr_t ip, uint16_t port, uint8_t *buf, int buf_len)
{struct sockaddr_in saddr = {0};int sock = -1;int len = 0;// 设置服务器地址saddr.sin_family = AF_INET;saddr.sin_port = lwip_htons(port);saddr.sin_addr.s_addr = ip.addr;sock = lwip_socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); // 建立socketif (sock < 0)return -1;len = lwip_sendto(sock, buf, buf_len, 0, (struct sockaddr *)&saddr, sizeof(saddr)); // 发送数据if (len < 0){ // 连接失败lwip_close(sock);return -1;}lwip_close(sock);return len;
}

UDP监听

步骤

  1. 建立套接字 (lwip_socket)
  2. 设置本地的端口 (lwip_bind)
  3. 读取数据 (lwip_recvfrom)

/*** @brief UDP读取回调* @param num UDP编号* @param ip 来源IP* @param port 来源端口* @param buf 数据* @param buf_len 数据长度* @author HZ12138* @date 2025-01-14 22:37:52*/
__WEAK void udp_read_callback(uint8_t num, ip4_addr_t ip, uint16_t port, uint8_t *buf, int buf_len)
{// printf("%s\n", ip4addr_ntoa(&ip));// printf("%d\n", port);printf("udp_%d:\n", num);uart_write_bytes(UART_NUM_0, (const char *)buf, buf_len);printf("\n");udp_send_data(ip, port, buf, buf_len);
}
/*** @brief UDP监听任务* @param pvParameters UDP编号* @author HZ12138* @date 2025-01-14 22:38:14*/
void udp_listen_task(void *pvParameters)
{int num = (int)pvParameters;int socket_num = udp_socket[num];int buf_len;ip4_addr_t ip;uint16_t port;uint8_t buf[1024];struct sockaddr_in client_addr;socklen_t client_addr_len = sizeof(client_addr);while (1){buf_len = lwip_recvfrom(socket_num, buf, sizeof(buf), 0, (struct sockaddr *)&client_addr, &client_addr_len);printf("udp:%d:\n", buf_len);if (buf_len > 0){port = lwip_ntohs(client_addr.sin_port);ip.addr = client_addr.sin_addr.s_addr;udp_read_callback(num, ip, port, buf, buf_len);}if (buf_len < 0)break;vTaskDelay(pdMS_TO_TICKS(10));}lwip_close(socket_num);vTaskDelete(NULL);
}/*** @brief UDP监听初始化* @param num UDP编号* @param port 端口* @return 错误码 -1为创建失败 0为创建成功* @author HZ12138* @date 2025-01-14 22:38:27*/
int udp_listen_init(uint8_t num, uint16_t port)
{int socket_num = -1;struct sockaddr_in saddr = {0};err_t err;if (num >= UDP_LISTEN_MAX_NUM)return -1;if (udp_socket[num] > 0)return -1;saddr.sin_family = AF_INET;saddr.sin_port = lwip_htons(port);saddr.sin_addr.s_addr = lwip_htonl(INADDR_ANY);socket_num = lwip_socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);if (socket_num < 0)return -1;err = lwip_bind(socket_num, (struct sockaddr *)&saddr, sizeof(saddr));if (err < 0){lwip_close(socket_num);return -1;}udp_socket[num] = socket_num;xTaskCreate(udp_listen_task, "udp_listen_task", 1024 * 2, (void *)num, 2, NULL);return 0;
}
/*** @brief 关闭UDP监听* @param num UDP编号* @author HZ12138* @date 2025-01-14 22:44:26*/
void udp_listen_close(uint8_t num)
{int socket_num;socket_num = udp_socket[num];if (socket_num > 0) // 在关闭前判断是否已经关闭lwip_close(socket_num);udp_socket[num] = 0;
}

MQTT

初始化客户端

输入输出
描述名称功能
const esp_mqtt_client_config_t *config配置参数
esp_mqtt_client_handle_t输出句柄
esp_mqtt_client_config_t 结构体
描述名称功能
const char *host域名或ip
uint32_tport端口
const char *username用户名
const char *password密码
```
esp_mqtt_client_handle_t esp_mqtt_client_init(const esp_mqtt_client_config_t *config)

设置事件回调

输入输出
描述名称功能
esp_mqtt_client_handle_tclient客户端句柄
esp_mqtt_event_id_tevent事件ID,ESP_EVENT_ANY_ID所有事件
esp_event_handler_tevent_handler回调函数
void*event_handler_arg参数
esp_err_t输出错误码
esp_err_t esp_mqtt_client_register_event(esp_mqtt_client_handle_t client, esp_mqtt_event_id_t event, esp_event_handler_t event_handler, void* event_handler_arg)

回调函数示例

输入输出
描述名称功能
void *event_handler_arg事件处理参数
esp_event_base_tevent_base事件唯一标识
int32_tevent_id事件ID
void*event_data数据MQTT_EVENT_DATA时是esp_mqtt_event_handle_t
void MQTT_event_fun(void *event_handler_arg, esp_event_base_t event_base, int32_t event_id, void *event_data)

开启客户端

输入输出
描述名称功能
esp_mqtt_client_handle_tclient客户端句柄
esp_err_t输出错误码
esp_err_t esp_mqtt_client_start(esp_mqtt_client_handle_t client)

订阅主题

输入输出
描述名称功能
esp_mqtt_client_handle_tclient客户端句柄
const char *topic主题
intqos0至少一次,1至多一次,2只有一次
esp_err_t输出错误码

连接到服务器后再调用

int esp_mqtt_client_subscribe(esp_mqtt_client_handle_t client, const char *topic, int qos)

重连

输入输出
描述名称功能
esp_mqtt_client_handle_tclient客户端句柄
esp_err_t输出错误码
esp_err_t esp_mqtt_client_reconnect(esp_mqtt_client_handle_t client)

发布

输入输出
描述名称功能
esp_mqtt_client_handle_tclient客户端句柄
const char *topic主题
const char *data数据
intlen数据长度
intqos0至少一次,1至多一次,2只有一次
intretain保留,一般写0即可
esp_err_t输出错误码

连接到服务器后再调用

int esp_mqtt_client_publish(esp_mqtt_client_handle_t client, const char *topic, const char *data, int len, int qos, int retain)

示例

bool MQTT_connect_OK = false; // MQTT连接状态标志
esp_mqtt_client_handle_t MQTT_client;/*** @brief MQTT读取回调函数* @param topic 主题* @param topic_len 主题长度* @param data 数据* @param data_len 数据长度* @author HZ12138* @date 2025-01-15 16:49:46*/
__WEAK void MQTT_read_callback(char *topic, int topic_len, char *data, int data_len)
{printf("topic:%s\n", topic);printf("data:%s\n", data);
}/*** @brief MQTT发布函数* @param topic 主题* @param data 数据* @param data_len 数据长度* @param qos 服务质量* @author HZ12138* @date 2025-01-15 16:50:20*/
void MQTT_pubslish(char *topic, uint8_t *data, int data_len, int qos)
{esp_mqtt_client_publish(MQTT_client, topic, (const char *)data, data_len, qos, 0);
}/*** @brief MQTT事件处理函数* @param event_handler_arg 事件处理参数* @param event_base 事件唯一标识* @param event_id 事件ID* @param event_data 事件数据* @author HZ12138* @date 2025-01-15 16:50:37*/
void MQTT_event_fun(void *event_handler_arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
{esp_mqtt_event_handle_t event;switch (event_id){case MQTT_EVENT_CONNECTED: // 连接上MQTT服务器MQTT_connect_OK = true;break;case MQTT_EVENT_DISCONNECTED: // 断开MQTT服务器连接 自动重连MQTT_connect_OK = false;esp_mqtt_client_reconnect(MQTT_client);break;case MQTT_EVENT_DATA:event = (esp_mqtt_event_handle_t)event_data; // 获取数据MQTT_read_callback(event->topic, event->topic_len, event->data, event->data_len);break;default:break;}
}
/*** @brief MQTT订阅任务* @param pvParameters 主题* @author HZ12138* @date 2025-01-15 16:51:18*/
void MQTT_subscribe_task(void *pvParameters)
{char topic[50];strcpy(topic, (char *)pvParameters);while (1){if (MQTT_connect_OK){esp_mqtt_client_subscribe(MQTT_client, topic, 1); // 订阅一个测试主题break;}vTaskDelay(pdMS_TO_TICKS(100));}vTaskDelete(NULL);
}
/*** @brief MQTT初始化函数* @param host 服务器地址* @param port 服务器端口* @param username 用户名* @param password 密码* @param topic 订阅主题* @author HZ12138* @date 2025-01-15 16:51:36*/
void MQTT_init(char *host, int port, char *username, char *password, char *topic)
{esp_mqtt_client_config_t mqtt_cfg = {.host = host,.port = port,.username = username,.password = password,};MQTT_client = esp_mqtt_client_init(&mqtt_cfg);esp_mqtt_client_register_event(MQTT_client, ESP_EVENT_ANY_ID, MQTT_event_fun, NULL);esp_mqtt_client_start(MQTT_client);xTaskCreate(MQTT_subscribe_task, "MQTT_subscribe_task", 1024, (void *)topic, 2, NULL);
}

SPISSF

分区表设置

根目录建立文件 partitions.csv

# Name,   Type, SubType, Offset,  Size, Flags
# Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild
nvs,      data, nvs,     0x9000,  0x6000,
phy_init, data, phy,     0xf000,  0x1000,
factory,  app,  factory, 0x10000, 712K,
storage,  data, spiffs,  ,        248K, 

进入配置页面 make menuconfig

选择分区表

选择使用外部文件

image-20250119230333875

image-20250119230433054

默认的文件名就是之前建立的文件,也可以这里修改

image-20250119230514745

只支持一级目录,基本目录需要写入分请设置文件

初始化

输入输出
描述名称功能
const esp_vfs_spiffs_conf_t *conf设置结构体
esp_err_t输出错误码
esp_vfs_spiffs_conf_t 结构体
描述名称功能
const char*base_path与文件系统关联的文件路径前缀
const char*partition_label可选,要使用的 SPIFFS 分区的标签。如果设置为 NULL,将使用具有 subtype=spiffs 的第一个分区
size_tmax_files可同时打开的最大文件数
boolformat_if_mount_failed如果为 True,则挂载失败时将格式化文件系统
esp_err_t esp_vfs_spiffs_register(const esp_vfs_spiffs_conf_t * conf)

读写等操作

使用标准C语言API即可

网页配网

先查看NVS中是否有可用的配置信息

如果有则连接wifi

如果无则扫描wifi后进入AP模式开启配网网页

调用SETNET_init()以开始

因用到主机名修改 要将LWIP_NETIF_HOSTNAME宏定义打开


#ifdef USE_WEBSETNET
// 用完后free
char *SETNET_AP_str;
typedef struct SETNET_data_t
{char ssid[32];char password[64];uint8_t mac[6];char name[32];uint8_t flag;
} SETNET_data_t;SETNET_data_t SETNET_data = {0};
QueueHandle_t SETNET_net_opt_OK;/*** @brief 获取AP列表字符串* @author HZ12138* @date 2025-01-17 23:07:21*/
void SETNET_get_ap_str(void)
{char *mytemp;wifi_ap_record_t ap_list[20];uint16_t ap_num = 0;// 扫描wifiwifi_STA_init("", "", NULL);wifi_scan(ap_list, &ap_num);// 初始化变量SETNET_AP_str = (char *)malloc(1024);mytemp = (char *)malloc(100);memset(SETNET_AP_str, 0, 1024);memset(mytemp, 0, 100);// 生成字符串for (int i = 0; i < ap_num; i++){strcat(SETNET_AP_str, (char *)ap_list[i].ssid); // 写入ssid// 写入rssimemset(mytemp, 0, 100);sprintf(mytemp, ",%d,", ap_list[i].rssi);strcat(SETNET_AP_str, mytemp);// 写入MACmemset(mytemp, 0, 100);sprintf(mytemp, MACSTR, ap_list[i].bssid[0], ap_list[i].bssid[1], ap_list[i].bssid[2], ap_list[i].bssid[3], ap_list[i].bssid[4], ap_list[i].bssid[5]);strcat(SETNET_AP_str, mytemp);// 去除最后一个逗号if (i == ap_num - 1)break;strcat(SETNET_AP_str, "|");}free(mytemp);
}/*** @brief 配网主任务* @author HZ12138* @date 2025-01-18 23:29:19*/
void SETNET_main_task(void *pvParameters)
{size_t NVS_read_len = 0xffff;NVS_read_data("SETNET_data", "SETNET", &SETNET_data, &NVS_read_len);// printf("NVS_read_len:%d flag:%d ssid:%s password:%s mac:" MACSTR " name:%s\n", NVS_read_len, SETNET_data.flag, SETNET_data.ssid, SETNET_data.password, SETNET_data.mac[0], SETNET_data.mac[1], SETNET_data.mac[2], SETNET_data.mac[3], SETNET_data.mac[4], SETNET_data.mac[5], SETNET_data.name);if (SETNET_data.flag == 0 || NVS_read_len == 0){// 先获取AP列表SETNET_get_ap_str();vTaskDelay(pdMS_TO_TICKS(1000));wifi_deinit();// 初始化为AP AP名为ESP-(本设备MAC号)uint8_t mac[6];char *SSID;SSID = (char *)malloc(100);memset(SSID, 0, 100);esp_efuse_mac_get_default(mac);sprintf(SSID, "ESP-%02X%02X%02X%02X%02X%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);wifi_AP_init(SSID, "");free(SSID);// 设置强制门户SETNET_net_opt_OK = xSemaphoreCreateBinary();DNS_start_server();web_start_any_path_server();// 等待网页获取到数据if (xSemaphoreTake(SETNET_net_opt_OK, portMAX_DELAY) == pdTRUE){vTaskDelay(pdMS_TO_TICKS(500));SETNET_data.flag = 1;// printf("flag:%d ssid:%s password:%s mac:" MACSTR " name:%s\n", SETNET_data.flag, SETNET_data.ssid, SETNET_data.password, SETNET_data.mac[0], SETNET_data.mac[1], SETNET_data.mac[2], SETNET_data.mac[3], SETNET_data.mac[4], SETNET_data.mac[5], SETNET_data.name);// 将配置写入NVSNVS_write_data("SETNET_data", "SETNET", &SETNET_data, sizeof(SETNET_data_t));// 重启wifi_deinit();esp_restart();}}else{if (SETNET_data.mac[0] != 0){ // 有指定MAC设置// printf("connecting to wifi with mac\n");wifi_STA_init(SETNET_data.ssid, SETNET_data.password, SETNET_data.mac);}else{ // 无指定MAC设置 通过SSID连接// printf("connecting to wifi without mac\n");wifi_STA_init(SETNET_data.ssid, SETNET_data.password, NULL);}// 有传入设备名if (strlen(SETNET_data.name) != 0)tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_STA, SETNET_data.name);while (1){// 等待连接成功if (wifi_STA_connect_success == 1){// printf("wifi OK,ip:%s \n", ip4addr_ntoa(&wifi_ip));break;}// 如果连接失败次数大于8次则重启if (wifi_STA_connect_fail_times > 8){// printf("wifi connect fail\n");// 清除标志SETNET_data.flag = 0;NVS_write_data("SETNET_data", "SETNET", &SETNET_data, sizeof(SETNET_data_t));esp_restart();}vTaskDelay(pdMS_TO_TICKS(100));}}vTaskDelete(NULL);
}/*** @brief 启动网页配网* @author HZ12138* @date 2025-01-18 23:28:38*/
void SETNET_init(void)
{xTaskCreate(SETNET_main_task, "SETNET_main_task", 1024 * 5, NULL, 2, NULL);
}/*** @brief 解析setweb参数* @param source 源字符串* @param ssid ssid* @param mac mac地址* @param password 密码* @param name 设备名* @author HZ12138* @date 2025-01-18 16:49:13*/
void setweb_get_opt(char *source, char *ssid, uint8_t *mac, char *password, char *name)
{// 如果有一个为空则退出if (source == NULL || ssid == NULL || mac == NULL || password == NULL || name == NULL)goto err;char mac_temp[100];uint64_t mac_temp_int = 0;// 解析参数sscanf(source, "ssid=%[^&]&mac=%[^&]&password=%[^&]&name=%s", ssid, mac_temp, password, name);if (strlen(mac_temp) != 12){memset(mac, 0, 6);goto err;}// 解析macfor (int i = 0; i < 12; i++){mac_temp_int <<= 4;if (mac_temp[i] >= '0' && mac_temp[i] <= '9')mac_temp_int += mac_temp[i] - '0';else if (mac_temp[i] >= 'A' && mac_temp[i] <= 'F')mac_temp_int += mac_temp[i] - 'A' + 10;else if (mac_temp[i] >= 'a' && mac_temp[i] <= 'f')mac_temp_int += mac_temp[i] - 'a' + 10;elsegoto err;}mac[0] = (mac_temp_int >> 40) & 0xff;mac[1] = (mac_temp_int >> 32) & 0xff;mac[2] = (mac_temp_int >> 24) & 0xff;mac[3] = (mac_temp_int >> 16) & 0xff;mac[4] = (mac_temp_int >> 8) & 0xff;mac[5] = mac_temp_int & 0xff;// printf("ssid:%s\n", ssid);// printf("mac:" MACSTR "\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);// printf("password:%s\n", password);// printf("name:%s\n", name);
err:return;
}/*** @brief 重新定义函数,如不用则关闭宏定义* @author HZ12138* @date 2025-01-18 17:04:25*/
int web_any_path_server_GET_callback(int (*mwrite)(int fd, void *buffer, int length), int socket_num, char *uri, char *body)
{char buf[300];// 从文件中读取网页extern const uint8_t setweb_html_start[] asm("_binary_setwifiweb_html_start");extern const uint8_t setweb_html_end[] asm("_binary_setwifiweb_html_end");// 发送网页sprintf(buf, HTTP_200, (int)(setweb_html_end - setweb_html_start) - 1);mwrite(socket_num, (void *)buf, strlen(buf));mwrite(socket_num, (void *)setweb_html_start, setweb_html_end - setweb_html_start - 1);return 0;
}/*** @brief 重新定义函数,如不用则关闭宏定义* @author HZ12138* @date 2025-01-18 17:04:25*/
int web_any_path_server_POST_callback(int (*mwrite)(int fd, void *buffer, int length), int socket_num, char *uri, char *body)
{if (strcmp(uri, "/getWIFI") == 0){ // 获取AP列表mwrite(socket_num, (void *)SETNET_AP_str, strlen(SETNET_AP_str));}else if (strcmp(uri, "/set_wifi") == 0){ // 设置wifichar res[300] = {0};// printf("body:%s\n", body);memset(SETNET_data.ssid, 0, 32);memset(SETNET_data.mac, 0, 6);memset(SETNET_data.password, 0, 64);memset(SETNET_data.name, 0, 32);// 解析参数setweb_get_opt(body, SETNET_data.ssid, SETNET_data.mac, SETNET_data.password, SETNET_data.name);// 发送设置信息后的页面sprintf(res, "connecting to ssid:%s mac:" MACSTR " and will set name as %s ....\n ESP will restart after 5s", SETNET_data.ssid,SETNET_data.mac[0], SETNET_data.mac[1], SETNET_data.mac[2], SETNET_data.mac[3], SETNET_data.mac[4], SETNET_data.mac[5],SETNET_data.name);mwrite(socket_num, (void *)res, strlen(res));// 给主任务发送信号xSemaphoreGive(SETNET_net_opt_OK);}return 0;
}
#endif

OTA

头文件esp_ota_ops.h

分区表设置

具体设置见SPISSF

此处给出OTA用的CSV文件示例

# Name,   Type, SubType, Offset,  Size, Flags
nvs,      data, nvs,     0x9000,  0x6000,
phy_init, data, phy,     0xf000,  0x1000,
factory,  app,  factory, 0x10000, 1M,
ota_0,  app,  ota_0, , 1M,
ota_1,  app,  ota_1, , 1M,
otadata,  data, ota,     ,        0x2000,
storage,  data, spiffs,  ,        500K, 

factory存放出厂应用

ota_0,ota_1存放OTA应用

otadata存放数据,建议放到出厂和OTA分区之后

出厂后运行在factory中的应用,

进行OTA是写入并重启后运行ota_0中的应用,

下次OTA是写入并重启后运行ota_1中的应用

再下次是ota_0,

以此类推

获取当前运行分区

输入输出
描述名称功能
const esp_partition_t输出分区信息
esp_partition_t 结构体
描述名称功能
esp_partition_type_ttype分区类型(app/data)
esp_partition_subtype_tsubtype分区子类型
uint32_taddress起始地址
uint32_tsize大小(bytes)
charlabel名称(char[17])
boolencrypted分区加密为true
const esp_partition_t* esp_ota_get_running_partition(void)

获取下一个分区

输入输出
描述名称功能
const esp_partition_t *start_from传入NULL表示当前即可
const esp_partition_t输出分区信息
const esp_partition_t* esp_ota_get_next_update_partition(const esp_partition_t *start_from)

打开OTA分区

输入输出
描述名称功能
const esp_partition_t *partition要写入的分区
size_timage_size镜像大小,可写(OTA_SIZE_UNKNOWN)
esp_ota_handle_t *out_handle(输出)句柄
esp_err_t输出错误码

获取操作句柄后写入数据

esp_err_t esp_ota_begin(const esp_partition_t *partition, size_t image_size, esp_ota_handle_t *out_handle)

写入数据

输入输出
描述名称功能
esp_ota_handle_thandle句柄
const void *data数据
size_tsize大小
esp_err_t输出错误码
esp_err_t esp_ota_write(esp_ota_handle_t handle, const void *data, size_t size)

结束写入

输入输出
描述名称功能
esp_ota_handle_thandle句柄
esp_err_t输出错误码

进行校验后关闭句柄

esp_err_t esp_ota_end(esp_ota_handle_t handle)

设置下次启动分区

输入输出
描述名称功能
const esp_partition_t *partition分区信息
esp_err_t输出错误码
esp_err_t esp_ota_set_boot_partition(const esp_partition_t *partition)

示例

const char *TGA_OTA = "OTA";/*** @brief ESP OTA 下载回调* @param socket_num 套接字号* @param para 参数* @author HZ12138* @date 2025-01-24 13:14:15*/
void OTA_esp_update_action(int socket_num, void *para)
{const esp_partition_t *update_partition = NULL;esp_ota_handle_t update_handle = 0;int read_num;uint8_t recv_buf[100];uint8_t *temp = NULL;esp_err_t err;update_partition = esp_ota_get_next_update_partition(NULL);// 读取第一帧 带有请求头memset(recv_buf, 0, 100);read_num = lwip_read(socket_num, recv_buf, sizeof(recv_buf) - 1);CHECK_OTA_VOID_LESS0(read_num, "OTA_esp_update_action: read failed");// 寻找成功标志temp = (uint8_t *)strstr((const char *)recv_buf, "200 OK");if (temp == NULL){ESP_LOGE(TGA_OTA, "OTA_esp_update_action: download failed");return;}// 寻找bodydo{ // 循环读取直到找到bodytemp = (uint8_t *)strstr((const char *)recv_buf, "\r\n\r\n"); // 寻找分隔符if (temp != NULL)break;// 读取新一帧memset(recv_buf, 0, 100);read_num = lwip_read(socket_num, recv_buf, sizeof(recv_buf) - 1);CHECK_OTA_VOID_LESS0(read_num, "OTA_esp_update_action: read failed");} while (read_num > 0);// 创建ota句柄CHECK_OTA_VOID_ERR(esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle), "OTA_esp_update_action: esp_ota_begin failed, error=%s");// 写入第一帧temp += 4;ESP_LOGI(TGA_OTA, "OTA_esp_update_action: write first frame");CHECK_OTA_VOID_ERR(esp_ota_write(update_handle, temp, read_num - (temp - recv_buf)), "OTA_esp_update_action: esp_ota_write first failed, err: %s");// uart_write_bytes(UART_NUM_0, "data:", 5);// uart_write_bytes(UART_NUM_0, (const char *)temp, read_num - (temp - recv_buf));// 写入其他帧do{// 读取帧memset(recv_buf, 0, 100);read_num = lwip_read(socket_num, recv_buf, sizeof(recv_buf) - 1);// 校验并写入CHECK_OTA_VOID_LESS0(read_num, "OTA_esp_update_action: read failed");CHECK_OTA_VOID_ERR(esp_ota_write(update_handle, recv_buf, read_num), "OTA_esp_update_action: esp_ota_write failed, err: %s");// uart_write_bytes(UART_NUM_0, (const char *)recv_buf, read_num);} while (read_num > 0);ESP_LOGI(TGA_OTA, "OTA_esp_update_action: update success");CHECK_OTA_VOID_ERR(esp_ota_end(update_handle), "OTA_esp_update_action: esp_ota_end failed,err: %s");                                  // 结束写入OTACHECK_OTA_VOID_ERR(esp_ota_set_boot_partition(update_partition), "OTA_esp_update_action: esp_ota_set_boot_partition failed,err: %s"); // 设置启动分区ESP_LOGI(TGA_OTA, "will restart");esp_restart();
}/*** @brief ESP OTA 任务* @param pvParameters 版本号* @author HZ12138* @date 2025-01-24 13:15:06*/
void OTA_esp_task(void *pvParameters)
{uint32_t version = (uint32_t)pvParameters;uint8_t v0, v1, v2;char url[100];esp_err_t err;// 拼接urlv0 = (version >> 16) & 0xff;v1 = (version >> 8) & 0xff;v2 = version & 0xff;sprintf(url, "http://" OTA_url_base OTA_url_path "/esp/%d.%d.%d", v0, v1, v2);ESP_LOGI(TGA_OTA, "OTA_ESP_url:%s", url);CHECK_OTA_VOID_ERR(web_http_request(OTA_url_base, url, "GET", "", OTA_esp_update_action, NULL), "OTA_esp_task: web_http_request failed, err: %s");vTaskDelay(pdMS_TO_TICKS(1000));vTaskDelete(NULL);
}/*** @brief ESP OTA 启动* @param version 版本号 0x010203->1.2.3* @author HZ12138* @date 2025-01-24 13:15:29*/
void OTA_esp_start(uint32_t version)
{xTaskCreate(OTA_esp_task, "OTA_esp_task", 1024 * 9, (void *const)version, 2, NULL);
}

相关文章:

ESPIDF备忘

ESP8266 环境搭建 Windows 首先确保安装好了vscode和git 在工作目录使用git 克隆这个 git clone --recursive https://github.com/espressif/ESP8266_RTOS_SDK.git下载 集成环境和 ESP8266编译工具 旧版本的集成工具可能有问题 这里用20200601版本的 https://dl.espressif.co…...

4-c语言中的数据类型

一.C 语⾔中的常量 1.生活中的数据 整数&#xff1a; 100,200,300,400,500 小数: 11.11 22.22 33.33 字母&#xff1a; a&#xff0c;b&#xff0c;c&#xff0c;d A&#xff0c;B&#xff0c;C&#xff0c;D 在 C 语⾔中我们把字⺟叫做字符. 字符⽤单引号引⽤。例如A’ 单词…...

ST 芯片架构全景速览:MCU、无线 SoC、BLE 模块、MPU 差异详解

在嵌入式开发中,ST 是一个非常常见的芯片厂商,其产品线覆盖了 MCU、无线芯片、BLE 模块以及运行 Linux 的 MPU 等多个领域。很多开发者初次接触 ST 时会对这些产品之间的关系感到困惑。 本文从分类视角出发,带你快速了解 ST 芯片家族的核心架构和主要用途。 🧭 ST 芯片四…...

第十章Python语言高阶加强-SQL(数据库)

目录 一.数据库介绍 二.MySQL的安装 三.MySQL入门使用 四.SQL基础和DDL 五.SQL—DML 六.SQL—DQL 1.基础查询 2.分组聚合 七.Python操作MySQL 1.基础使用 2.数据插入 此章节主要掌握并且了解SQL语法和基础使用。如&#xff1a;安装、增删改查&#xff0c;SQL在未来我…...

NO.71十六届蓝桥杯备战|搜索算法-递归型枚举与回溯剪枝|枚举子集|组合型枚举|枚举排列|全排列问题(C++)

什么是搜索&#xff1f; 搜索&#xff0c;是⼀种枚举&#xff0c;通过穷举所有的情况来找到最优解&#xff0c;或者统计合法解的个数。因此&#xff0c;搜索有时候也叫作暴搜。 搜索⼀般分为深度优先搜索(DFS)与宽度优先搜索(BFS)。深度优先遍历vs深度优先搜索&#xff0c;宽度…...

C++ 中为什么构造函数不需要实现虚函数,而析构函数需要?

在C中&#xff0c;构造函数不需要是虚函数&#xff0c;而析构函数往往需要&#xff0c;原因如下&#xff1a; 构造函数 对象创建顺序&#xff1a;构造函数的主要任务是初始化对象的成员变量&#xff0c;创建对象时需要先调用基类的构造函数&#xff0c;再调用派生类的构造函数…...

如何获取oracle cloud永久免费的vps(4C/24G)?

1.注册oracle cloud账号 Oracle Cloud 免费套餐 | Oracle 中国 新注册的小伙伴&#xff0c;可以在 30 天内&#xff0c;利用 300 美元免费储值&#xff0c;任性使用所有 Oracle Cloud 基础设施服务。 30 天后呢&#xff1f;你仍然可以畅享 Always Free 免费套餐中的云服务&am…...

TypeScript面试题集合【初级、中级、高级】

初级面试题 什么是TypeScript&#xff1f; TypeScript是JavaScript的超集&#xff0c;由Microsoft开发&#xff0c;它添加了可选的静态类型和基于类的面向对象编程。TypeScript旨在解决JavaScript的某些局限性&#xff0c;比如缺乏静态类型和基于类的面向对象编程&#xff0c…...

java面试篇 并发编程篇

目录 1.线程的基础知识 1.线程与进程的区别&#xff1f; 2.并行与并发的区别&#xff1f; 3.创建线程的方式有哪些&#xff1f; 4.线程包括哪些状态&#xff1f;状态之间是如何变化的&#xff1f; 5.T1&#xff0c;T2&#xff0c;T3三个线程新建完毕后&#xff0c;如何保证…...

掌握 JSON 到表格转换:全面指南

高效地转换数据格式对于现代数据处理至关重要。JSON&#xff08;JavaScript 对象表示法&#xff09;因其灵活性而广受欢迎&#xff0c;而 CSV 或 Excel 等表格格式则更适用于数据分析和可视化。本文将介绍多种 JSON 转换为表格格式的方法&#xff0c;帮助您提升数据处理和可视化…...

【Spring Cloud Netflix】GateWay服务网关

1.基本概述 GateWay用于在微服务架构中提供统一的入口点&#xff0c;对请求进行路由&#xff0c;过滤和处理。它就像是整个微服务系统的大门&#xff0c;所有外部请求都要通过它才能访问到后端的各个微服务。 2.核心概念 2.1路由(Route) 路由是Spring Cloud gateWay中最基本…...

国产芯片解析:龙讯USB Type-C/DP Transmitter多场景覆盖,定义高速互联新标杆

在智能设备功能日益复杂化的今天&#xff0c;高速数据传输、高清视频输出与多功能接口融合已成为行业刚需。龙讯半导体&#xff08;Lontium&#xff09;凭借其领先的芯片设计能力&#xff0c;推出多款‌USB Type-C/DP Transmitter芯片‌&#xff0c;覆盖从消费电子到工业应用的…...

蓝桥杯 web 展开你的扇子(css3)

普通答案&#xff1a; #box:hover #item1{transform: rotate(-60deg); } #box:hover #item2{transform: rotate(-50deg); } #box:hover #item3{transform: rotate(-40deg); } #box:hover #item4{transform: rotate(-30deg); } #box:hover #item5{transform: rotate(-20deg); }…...

【Kubernetes】StorageClass 的作用是什么?如何实现动态存储供应?

StorageClass 使得用户能够根据不同的存储需求动态地申请和管理存储资源。 StorageClass 定义了如何创建存储资源&#xff0c;并指定了存储供应的配置&#xff0c;例如存储类型、质量、访问模式等。为动态存储供应提供了基础&#xff0c;使得 Kubernetes 可以在用户创建 PVC 时…...

4月7号.

双列集合的特点: Map中的常见API: //1.创建Map集合的对象 Map<String, String> m new HashMap<>();//2.添加元素 Stringvalue1 m.put("郭靖","黄蓉"); System.out.println(value1); m.put("韦小宝","沐剑屏"); m.put(&q…...

C++抽卡模拟器

近日在学校无聊&#xff0c;写了个抽卡模拟器供大家娱乐。 代码实现以下功能&#xff1a;抽卡界面&#xff0c;抽卡判定、动画播放、存档。 1.抽卡界面及判定 技术有限&#xff0c;不可能做的和原神一样精致。代码如下&#xff08;注&#xff1a;这不是完整代码&#xff0c;…...

蓝桥杯 2. 开赛主题曲【算法赛】

2.开赛主题曲【算法赛】 - 蓝桥云课 这道题和3. 无重复字符的最长子串 - 力扣&#xff08;LeetCode&#xff09;类似&#xff0c;因为题目中规定只有小写字母&#xff0c;所以定义统计数组时只需要定义26个字母即可&#xff0c;然后每次遍历的字符减去‘a’即为他的下标 impo…...

Spring Boot 中的 Bean

2025/4/6 向全栈工程师迈进&#xff01; 一、Bean的扫描 在之前&#xff0c;对于Bean的扫描&#xff0c;我们可以在XML文件中书写标签&#xff0c;来指定要扫描的包路径&#xff0c;如下所示,可以实通过如下标签的方式&#xff1a; <context:component-scan base-package&…...

基于springboot科研论文检索系统的设计(源码+lw+部署文档+讲解),源码可白嫖!

摘要 随着我国经济的高速发展与人们生活水平的日益提高&#xff0c;人们对生活质量的追求也多种多样。尤其在人们生活节奏不断加快的当下&#xff0c;人们更趋向于足不出户解决生活上的问题&#xff0c;线上管理系统展现了其蓬勃生命力和广阔的前景。与此同时&#xff0c;在此…...

Mysql入门

一、数据库三层结构 所谓安装Mysql数据库&#xff0c;就是在主机安装一个数据库管理系统(DBMS)&#xff0c;这个管理程序可以管理多个数据库。DBMS(database manage system)一个数据库中可以创建多个表&#xff0c;以保存数据(信息)。数据库管理系统(DBMS)、数据库和表的关系如…...

如何解决uniapp打包安卓只出现功能栏而无数据的问题

如何解决uniapp打包安卓只出现功能栏而无数据的问题 经验来自&#xff1a;关于Vue3中调试APP触发异常&#xff1a;exception:white screen cause create instanceContext failed,check js stack -> at useStore (app-service.js:2309:15)解决方案 - 甲辰哥来帮你算命 - 博客…...

Python高级爬虫之JS逆向+安卓逆向1.1节-搭建Python开发环境

目录 引言&#xff1a; 1.1.1 为什么要安装Python? 1.1.2 下载Python解释器 1.1.3 安装Python解释器 1.1.4 测试是否安装成功 1.1.5 跟大神学高级爬虫安卓逆向 引言&#xff1a; 大神薯条老师的高级爬虫安卓逆向教程&#xff1a; 这套爬虫教程会系统讲解爬虫的初级&…...

剖析Mybatis中用到的设计模式

框架对很多设计模式的实现&#xff0c;都并非标准的代码实现&#xff0c;都做了比较多的自我改进。实际上&#xff0c;这就是所谓的灵活应用, 只借鉴不照搬, 根据具体问题针对性地去解决。 涉及到Mybatis源码知识和设计模式&#xff0c;这点我之前的博客均有涉及。 可以查看我的…...

MySQL 函数(入门版)

目录 一、字符串函数 1、常用的字符串函数 2、函数演示 3、具体案例 二、数值函数 1、常用的数值函数 2、函数演示 3、具体案例 三、日期函数 1、常用的日期函数 2、函数演示 3、具体案例 四、流程函数 1、常用的流程函数 2、函数演示 3、具体案例 在MySQL中&a…...

d202546

目录 一、罗马字符转数字 二、二叉树的右视图 三、sql-查询结果的质量和占比 一、罗马字符转数字 首先把两个字符组成一个数字做一下单独判断 然后就是单个字符转数字 public int romanToInt(String s) {int sum 0;char[] ch s.toCharArray();int i 0;while(i < ch.len…...

洛谷每日1题-------Day37__P1634 禽兽的传染病

题目背景 mxj 的启发。 题目描述 禽兽患传染病了。一个禽兽会每轮传染 x 个禽兽。试问 n 轮传染后有多少禽兽被传染&#xff1f; 输入格式 两个非负整数 x 和 n。 输出格式 一个整数&#xff0c;即被传染的禽兽数。 输入输出样例 输入 #1复制 10 2 输出 #1复制 121…...

c语言修炼秘籍 - - 禁(进)忌(阶)秘(技)术(巧)【第五式】动态内存管理

c语言修炼秘籍 - - 禁(进)忌(阶)秘(技)术(巧)【第五式】动态内存管理 【心法】 【第零章】c语言概述 【第一章】分支与循环语句 【第二章】函数 【第三章】数组 【第四章】操作符 【第五章】指针 【第六章】结构体 【第七章】const与c语言中一些错误代码 【禁忌秘术】 【第一式…...

理解进程和线程的概念

在操作系统中&#xff0c;进程和线程都是执行的基本单位&#xff0c;但它们在性质和管理方面有所不同 进程 定义: 进程是一个正在运行的程序的实例&#xff0c;是操作系统资源分配的基本单位。特点: 独立性&#xff1a;每个进程有其独立的内存空间、数据栈和其他辅助数据。重…...

【项目管理】第2章 信息技术发展 --知识点整理

Oracle相关文档,希望互相学习,共同进步 风123456789~-CSDN博客 (一)知识总览 对应:第1章-第5章 (二)知识笔记 二、信息技术的发展 1. 信息技术及其发展 1)计算机软硬件 计算机硬件由电子机械、光电元件等组成的物理装置,提供物质基础给计算机软件运行。软件包括程…...

docker部署rabbitmq

拉取镜像 docker pull rabbitmq:managementmanagement 标签表示包含管理插件&#xff0c;可以通过 Web 界面管理 RabbitMQ 启动容器 docker run --restartalways -d --name rabbitmq \-p 9004:5672 -p 9005:15672 \-e RABBITMQ_DEFAULT_USERrabbitmq \-e RABBITMQ_DEFAULT_P…...

手搓多模态-03 顶层和嵌入层的搭建

声明&#xff1a;本代码非原创&#xff0c;是博主跟着国外大佬的视频教程编写的&#xff0c;本博客主要为记录学习成果所用。 我们首先开始编写视觉模型这一部分&#xff0c;这一部分的主要功能是接收一个batch的图像&#xff0c;并将其转化为上下文相关的嵌入向量&#xff0c;…...

计算机系统---UEFI(统一可扩展固件接口)

一、UEFI定义与历史背景 UEFI&#xff08;Unified Extensible Firmware Interface&#xff09; 是替代传统BIOS的新一代固件接口标准&#xff0c;由UEFI论坛&#xff08;前身是Intel的EFI论坛&#xff09;制定&#xff0c;旨在解决BIOS在现代硬件和操作系统下的局限性。 设计…...

2025年渗透测试面试题总结-某四字大厂面试复盘扩展 二面 (题目+回答)

网络安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 某四字大厂面试复盘 二面 一、文件上传绕过WAF的15种深度技巧 二、SSRF利用与WAF绕过的8种高阶手法 …...

基于BP神经网络的杂草智能识别系统(杂草识别、Python项目)

基于BP神经网络的杂草智能识别系统 项目介绍 本项目是一个基于PyQt5和BP神经网络的杂草智能识别系统。系统通过图像处理和神经网络技术&#xff0c; 能够识别8种不同的杂草类别。用户可以通过上传图片&#xff0c;系统会自动识别图片中的杂草类别&#xff0c;并显示识别结果和…...

Java的Selenium的特殊元素操作与定位之时间日期控件

分为两种情况: 控件没有限制手动输入&#xff0c;则直接调用sendKeys方法写入时间数据 //时间日期控件处理 chromeDriver.get ("https://www,fliggy,com/?ttidsem.000000736&hlreferidbaidu.082076&route sourceseo"); chromeDriver.findElement (By.xpat…...

深入理解矩阵乘积的导数:以线性回归损失函数为例

深入理解矩阵乘积的导数&#xff1a;以线性回归损失函数为例 在机器学习和数据分析领域&#xff0c;矩阵微积分扮演着至关重要的角色。特别是当我们涉及到优化问题&#xff0c;如最小化损失函数时&#xff0c;对矩阵表达式求导变得必不可少。本文将通过一个具体的例子——线性…...

基于大数据的美团外卖数据可视化分析系统

【大数据】基于大数据的美团外卖数据可视化分析系统 &#xff08;完整系统源码开发笔记详细部署教程&#xff09;✅ 目录 一、项目简介二、项目界面展示三、项目视频展示 一、项目简介 该系统通过对海量外卖数据的深度挖掘与分析&#xff0c;能够为美团外卖平台提供运营决策支…...

TypeScript 类型系统详解

基础类型​ TypeScript 支持丰富的基础数据类型&#xff0c;涵盖number、string、boolean、null、undefined、symbol以及bigint。这些类型为构建可靠的代码提供了基石。​ 数值类型&#xff08;number&#xff09;&#xff1a;在 TypeScript 里&#xff0c;所有数字均为浮点数…...

【学Rust写CAD】33 近似 Alpha 混合函数(argb.rs补充方法)

源码 #[inline]pub fn over(self, dst: Argb) -> Argb {let a 256 - self.alpha32();let rb (dst.rb() * a) >> 8;let ag dst.ag() * a;Argb(self.0 (rb & Argb::MASK) | (ag & !Argb::MASK))}源码分析 这段代码实现了一个近似 Alpha 混合&#xff08;“…...

4.3-4.6学习总结 Java:Set系列集合+双列集合+Map

Set系列集合&#xff1a; 元素是唯一的。 HashSet&#xff1a; 哈希值&#xff1a; 如果没有重写hashcode方法和equals方法&#xff0c;那么哈希值是根据地址值计算的。 LinkedHashSet&#xff1a; TreeSet底层为红黑树。 红黑树&#xff1a;两个红色节点不能相连。 双列集合&…...

深信服护网蓝初面试题

《网安面试指南》https://mp.weixin.qq.com/s/RIVYDmxI9g_TgGrpbdDKtA?token1860256701&langzh_CN 5000篇网安资料库https://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247486065&idx2&snb30ade8200e842743339d428f414475e&chksmc0e4732df793fa3bf39…...

2011-2019年各省地方财政商业服务业等事务支出数据

2011-2019年各省地方财政商业服务业等事务支出数据 1、时间&#xff1a;2007-2019年 2、来源&#xff1a;国家统计局、统计年鉴 3、指标&#xff1a;行政区划代码、地区、年份、地方财政商业服务业等事务支出 4、范围&#xff1a;31省 5、指标说明&#xff1a;地方财政在商…...

InfoSec Prep: OSCP靶场渗透

InfoSec Prep: OSCP InfoSec Prep: OSCP ~ VulnHubInfoSec Prep: OSCP, made by FalconSpy. Download & walkthrough links are available.https://www.vulnhub.com/entry/infosec-prep-oscp,508/ 1&#xff0c;将两台虚拟机网络连接都改为NAT模式 2&#xff0c;攻击机上做…...

【Kubernetes】Kubernetes 如何管理存储?PV 和 PVC 是如何工作的?

在 Kubernetes 中&#xff0c;存储管理是通过 Persistent Volume (PV) 和 Persistent Volume Claim (PVC) 来实现的。 它们为容器化应用提供持久化存储&#xff0c;使得应用在重启或重新调度时仍然能够访问数据。 1. 存储管理概述 Kubernetes 的存储管理是基于 持久化卷 (Pe…...

CasaOS小主机本地安装1Panel运维面板结合内网穿透移动端远程运维

文章目录 前言1. 添加镜像源2. 部署1Panel3. 本地访问测试4. 安装内网穿透工具5. 配置公网地址6. 配置固定公网地址 前言 小伙伴们&#xff0c;是不是一想到要远程管理服务器就头大如斗&#xff1f;特别是没有公网IP或路由器设置复杂时&#xff0c;简直让人抓狂&#xff01;但…...

BGP路由协议之属性2

Orgin 起源 公认必遵属性 起源名称标记描述IGPi如果路由是由始发的 BGP 路由器使用 network 命令注入到 BGP 的&#xff0c;那么该 BGP 路由的 origin 属性为 IGPEGPe如果路由是通过 EGP 学习到的&#xff0c;那么该 BGP 路由的 Origin 属性为 EGPIncomplete?如果路由是通过…...

HttpClient(笔记)

介绍&#xff1a; 发送请求的步骤&#xff1a; 发送get类型的请求&#xff1a; 发送post类型的请求&#xff1a;...

【简历全景认知】简历的历史演变与当代定位:从羊皮卷到算法博弈的艺术

引言:时间维度下的求职凭证。 当古埃及工匠在莎草纸上记录自己的技能时,他们不会想到这份"技能清单"会在四千年后演变成LinkedIn上的数字档案。简历作为个人职业身份的载体,其演变史就是一部微观的人力资源配置史。从手写推荐信到ATS系统解析的PDF,每一次形式变…...

ERP系统五大生产模式概述

制造业中,选择合适的生产模式是企业高效运营的关键。 以下是ERP系统支持的五大核心生产模式及其特点总结: 1. MTS(按库存生产) - 定义:先生产后销售,基于需求预测提前备货。 - 适用场景:需求稳定、标准化程度高的产品(如日用品、家电)。 - 优点:交货快、生产…...

003 vue生命周期和钩子函数

文章目录 1.Vue实例有一个完整的生命周期&#xff0c;也就是说从开始创建、初始化数据、编译模板、挂载DOM、渲染-更新-渲染、卸载等一系列过程&#xff0c;称之为Vue实例的生命周期 2.钩子函数&#xff08;监听函数&#xff09;&#xff1a;Vue实例在完整的生命周期过程中&…...