从零开始学ESP32:(二) 开启ESP32WIFI -STA和AP模式共存

2023-05-16

从零开始学ESP32:个人笔记记录:

芯片型号: ESP32
网络环境支持:LWIP
IDF.PY-SDK: ESP-IDF v4.3
芯片功能: 支持STA/AP网络共存模式:

工程: esp-idf/examples/get-started/hello_world
例程: esp-idf/examples/wifi/getting_started/station/main


基本头文件:

#include "nvs_flash.h"
#include "lwip/err.h"
#include "lwip/sys.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"

Cmakelisys.txt配置

idf_component_register(SRCS "test_wlan.c"
                    INCLUDE_DIRS "."
                    INCLUDE_DIRS "$ENV{IDF_PATH}/components/spi_flash/include"
                    INCLUDE_DIRS "$ENV{IDF_PATH}/components/nvs_flash/include"
                    )

component.mk配置

COMPONENT_ADD_INCLUDEDIRS := .

基本实现代码和解析:

#define WIFI_CONNECTED_BIT          BIT0
#define WIFI_FAIL_BIT               BIT1
#define EXAMPLE_ESP_MAXIMUM_RETRY  10

#define AP_WIFI_SSID 				"ESP32-AP-MODU" //创建出来的AP模式WIFI(热点)
#define AP_WIFI_PASS 				"123456789"     //AP模式密码

#define STA_WIFI_SSID				"my_wifi"    //连接路由WIFI的账号
#define STA_WIFI_PASS   			"123456789"  //连接路由WIFI的密码

//重封装打印
#define D_BUG(frtc, frtl...)    \ 
	    do{ ESP_LOGI( __FUNCTION__ , frtc,##frtl); } while(0) 

static EventGroupHandle_t s_wifi_event_group;  //全局事件句柄



void W_staDisConnectedEv(void)
{
    if(s_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY)
    {
        esp_wifi_connect();
        s_retry_num++;
        D_BUG("retry to connect to the AP \r\n");
    }
    else
    {
        xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
    }
    D_BUG("connect to the AP fail \r\n");
    return ;
}


void W_apStaConnectedEv( void* event_data  )
{
    wifi_event_ap_staconnected_t* event = (wifi_event_ap_staconnected_t*) event_data;

    D_BUG("station "MACSTR" join, AID=%d", MAC2STR(event->mac), event->aid);
    /* 一台设备连接到ESP32 */
}

void W_apStaDisConnectEv( void* event_data )
{
    wifi_event_ap_stadisconnected_t* event = (wifi_event_ap_stadisconnected_t*) event_data;
    D_BUG("station "MACSTR" leave, AID=%d", MAC2STR(event->mac), event->aid); 
    /* 一台设备离开了ESP32 */
}


void W_ipGotHostAddr( void* event_data )
{
    ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
    D_BUG("got ip:" IPSTR, IP2STR(&event->ip_info.ip) );
    s_retry_num = 0;
    xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
}

/*************************
 * 函数;W_eventHandler 
 * 
 * 
 * 说明: 事件处理回调函数
 *  */
 
static void W_eventHandler( 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; // STA START
            case WIFI_EVENT_STA_STOP:           D_BUG("STA stop\r\n"); break; // STA STOP 
            case WIFI_EVENT_STA_DISCONNECTED:   W_staDisConnectedEv();             break; // STA RECONECT 
            case WIFI_EVENT_AP_START:           D_BUG("AP start\r\n"); break; // AP  START 
            case WIFI_EVENT_AP_STOP:            D_BUG("AP stop\r\n");  break; // AP  STOP
            case WIFI_EVENT_AP_STACONNECTED:    W_apStaConnectedEv( event_data );  break; // 一台设备连接AP
            case WIFI_EVENT_AP_STADISCONNECTED: W_apStaDisConnectEv(event_data );  break; // 一台设备断开AP                             
            default:  break;
        }
    }
    else if( event_base == IP_EVENT )  // 路由事件ID 组
    {
        switch ( event_id )
        {
            case IP_EVENT_STA_GOT_IP:        W_ipGotHostAddr(event_data);       break; //获取到指定IP
            case IP_EVENT_STA_LOST_IP:       D_BUG("AP stop\r\n");  break;
            case IP_EVENT_AP_STAIPASSIGNED:  D_BUG("AP stop\r\n");  break;
            default:  break;
        }
    }
    return ;
}



/*******************************
 * 函数: WLAN_netConfDev
 *
 * 
 * 说明: WIFI - AP -STA 共存模式同时开启函数
 *  */
void WLAN_netConfDev(char * ssid_sta , char * pwssd_sta )
{
	int  STA_MAX_RETRY_NUM = 10;  // sta 模式下最大连接次数
    int  AP_WIFI_CHANNEL   = 13;  // AP 模式下得通道
    int  AP_MAX_STA_CONN   = 3;   // AP支持最大STA连接数 - 允许最大连接数量
    
    esp_event_handler_instance_t instance_any_id = {0};  //处理ID 实例句柄
    esp_event_handler_instance_t instance_got_ip = {0};  //处理IP 实例句柄
    
    s_wifi_event_group = xEventGroupCreate();            // 创建全局事件 
    ESP_ERROR_CHECK(esp_netif_init());                   // 网络子系统底层初始化
    ESP_ERROR_CHECK(esp_event_loop_create_default());    // 创建默认循环发送队列
    esp_netif_create_default_wifi_sta();		         //创建网络配置-默认是STA 
    
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); // WIFI设备配置
    ESP_ERROR_CHECK(esp_wifi_init(&cfg)); 			     // WIFI设备初始化
   
    /* 创建ID 事件实例回调 
     * WIFI_EVENT       = WIFI事件
     * ESP_EVENT_ANY_ID = 监听所有得ID事件
     * W_eventHandler   = 回调函数
     * NULL
     * instance_any_id  = 处理ID实例句柄
     * */
    ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
                                                        ESP_EVENT_ANY_ID,
                                                        &W_eventHandler,
                                                        NULL,
                                                        &instance_any_id));
    /* 创建IP 事件实例回调  
     * WIFI_EVENT          = WIFI事件
     * IP_EVENT_STA_GOT_IP = STA事件
     * W_eventHandler      = 回调函数
     * NULL
     * instance_any_id  = 处理IP实例句柄
     * */
    
    ESP_ERROR_CHECK( esp_event_handler_instance_register(IP_EVENT,
                                                        IP_EVENT_STA_GOT_IP,
                                                        &W_eventHandler,
                                                        NULL,
                                                        &instance_got_ip) );
                                                     
/* 创建 并 初始化STA对象 */
	    wifi_config_t wifi_configSta = {
        .sta = 
        {
	        /*ssid 和 password 成员在结构体里面是数组,不能直接将指针丢给它 
	         * 在外面赋值 
	         * strcpy( (char *) wifi_configSta.sta.ssid     ,  STA_WIFI_SSID);
	         * strcpy( (char *) wifi_configSta.sta.password ,  STA_WIFI_PASS);
	        */
          // .ssid     = STA_WIFI_SSID;
         // .password = STA_WIFI_PASS;				
	     .threshold.authmode = WIFI_AUTH_WPA2_PSK,
         .pmf_cfg = {
                .capable = true,
                .required = false,
            },
        },
    };
   strcpy( (char *) wifi_configSta.sta.ssid     ,  ssid_sta );
   strcpy( (char *) wifi_configSta.sta.password ,  pwssd_sta);
	/* 创建 并 初始化AP对象 */
    wifi_config_t wifi_configAP = {
        .ap = 
        {
            .ssid           = AP_WIFI_SSID,
            .ssid_len       = strlen( AP_WIFI_SSID ),
            .channel        = AP_WIFI_CHANNEL,
            .password       = AP_WIFI_PASS,
            .max_connection = AP_MAX_STA_CONN,
            .authmode       = WIFI_AUTH_WPA_WPA2_PSK,
        }
    };

/*** 
 * 注意: 将两个成员合成一个结构体里面然后 进行配置的时候,是无效的
 * 必须是STA 和 AP 各自一个结构体成员
 *  */
    // 设置WIFI为 AP-STA 共存模式
    ESP_ERROR_CHECK(esp_wifi_set_mode( WIFI_MODE_APSTA) );
	// 设置AP模式配置
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP , &wifi_configAP) );
	// 设置STA模式配置
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_configSta) );

    ESP_ERROR_CHECK(esp_wifi_start() ); 
    D_BUG("wifi_init_sta finished.\r\n");
    /* 等待全局事件发生 -- 发生对应事件就等于完成一个动作 */
    EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
                                            WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
                                            pdFALSE,
                                            pdFALSE,
                                            portMAX_DELAY);
            
    if (bits & WIFI_CONNECTED_BIT) 
    {
        D_BUG("connected to ap SSID:%s password:%s \r\n",ssid, pwssd);
    }
    else if (bits & WIFI_FAIL_BIT) 
    {
        D_BUG("Failed to connect to SSID:%s, password:%s \r\n",ssid, pwssd);
    } 
    else 
    {
        D_BUG("UNEXPECTED EVENT \r\n");
    }

/* 清除当前申请的设备句柄 */
    ESP_ERROR_CHECK(esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, instance_got_ip)); 
    ESP_ERROR_CHECK(esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID , instance_any_id)); 
    /* 删除当前事件组 */
    vEventGroupDelete(s_wifi_event_group);
}



/* 主函数进行调用 */
int app_main( void )
{
	WLAN_netConfDev( STA_WIFI_SSID ,  STA_WIFI_PASS );
}



/* 完 */

额外提及一下设置AP的地址和网关,子网掩码等等信息 参考下面


void W_setApDhcpInfo()
{
	tcpip_adapter_ip_info_t  apNetConfInfo  = {0};
	//初始化网络和DHCP有关内存,必须要提前执行这个API 不然下面操作直接让设备重启
	tcpip_adapter_init();  
	// 将AP模式的DHCP 模式先关闭
	tcpip_adapter_dhcps_stop ( TCPIP_ADAPTER_IF_AP );
	//将对应的地址转成对应的字节序, 要注意apNetConfInf成员是结构体嵌套结构体,才是数组
	//
	IP4_ADDR( &apNetConfInfo.ip      , 192 , 168 , 1   , 1);  //设置IP
	IP4_ADDR( &apNetConfInfo.gw      , 192 , 168 , 1   , 1);  //设置网关
	IP4_ADDR( &apNetConfInfo.netmask , 255 , 255 , 255 , 0);  //设置子网掩码
	
	tcpip_adapter_set_ip_info( TCPIP_ADAPTER_IF_AP , &apNetConfInfo ); //设置IP信息
	
	tcpip_adapter_dhcps_stop(  TCPIP_ADAPTER_IF_AP ); //重新启动AP模式 DHCP 
}

这个函数可以上面的 AP/STA 模式启动之后执行 

成功之后正常情况是:
连接上当前路由的WIFI 并且打印处对应的IP
然后使用手机搜索WIFI的时候会发现多出一个$(AP_WIFI_SSID)名称的热点。


说明:笔记使用-记录(抄袭我就吊你 J J )
关注微信公众号 一起学习 ( 技术Code城 )

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

从零开始学ESP32:(二) 开启ESP32WIFI -STA和AP模式共存 的相关文章

  • 【低功耗蓝牙】① 蓝牙广播数据格式分析

    摘要 本文章主要讲解了蓝牙的发展史 蓝牙信号 蓝牙广播数据的格式 最后使用ESP32芯片MicroPython固件给出了蓝牙广播的具体代码 是蓝牙初学者很好的参考资料 也可以参考下我在B站的蓝牙视频教程 ESP32教程 第二章 低功耗蓝牙B
  • esp32-S3专题一:初识esp32

    初识esp32 S3 初识esp32 使用esp32 s3这款芯片开发接近一年了 分享一下开发中的一些经验和项目中的亮点 希望后人能从中借鉴到一些经验 一 优势和缺点 使用的是esp32 s3这款芯片 硬件上搭配300k的RAM的代码运行内
  • ESP32s3 MSC/U盘 虚拟串口

    ESP32s3 MSC U盘 虚拟串口 开发环境是 IDF4 4 芯片 esp32s3 在项目中想要用到把内部的FLASH做成U盘 但是在idf4 4中没有找到MSC相关例程 如图 1 我在网上只找到esp32s2的例程 https git
  • Arduino IDE编译烧写ESP32 CAM

    一 安装Arduino IED 到官网下载IDE 二 安装ESP32 工具 打开菜单 文件 首选项 在设置页 附加开发板管理器网址 添加 https dl espressif com dl package esp32 index json
  • Ubuntu20.04配置ESP32-IDFV5.1环境及Component工程样例

    更新Ubuntu20 04下载源 cd etc apt 更新sources list为如下下载源 并保存 添加阿里源 deb http mirrors aliyun com ubuntu focal main restricted univ
  • ESP32+Arduino环境搭建教程 合宙ESP32C3

    1 在arduino官网下载安装包并安装 下载地址 https www arduino cc en software 2 安装Arduino对ESP32支持 1 添加ESP32开发板管理器地址 点击文件 gt 首选项 gt 其他开发板管理器
  • 又搞定一块屏幕 micropython 驱动 sharp memory lcd

    先来学学C是怎么写的 核心代码 void lcd refresh void uint16 t i 0 ln 0 SCS 1 delay us 5 write byte 0x80 for ln 0 ln lt 128 ln write byt
  • ESP32使用Arduino发布和订阅MQTT

    该项目展示了如何在ESP32上使用MQTT通信协议来发布消息和订阅主题 举例来说 我们会将BME280传感器的读数发布到Node RED仪表板 并控制ESP32输出 我们将使用Arduino IDE对ESP32进行编程 介绍 在此示例中 有
  • 单片机开发---ESP32S3移植lvgl+触摸屏

    书接上文 单片机开发 ESP32 S3模块上手 本章内容 熟悉一下ESP32S3的开发 修改范例程序的lvgl 使之能够匹配现在的显示屏 具体工作大概为通过SPI接口连接一块SPI串口屏幕 并且适配lvgl 最后加上触摸屏作为输入 屏幕 用
  • ESP32(MicroPython) 编码器电机闭环控制

    本人最近查找资料时 发现ESP32上的使用MicroPython的编码器电机相关程序较少 闭环控制程序都是Pyboard上的 与ESP32不完全兼容 本人通过micropython编程 esp32 drv8833 霍尔编码器 micropy
  • ESP32的液晶中文显示

    上一篇简单说明了ESP32进行SSD1306的液晶显示 然后希望显示字符 使用前一篇的SSD1306是一个入门 但是不能显示中文 因此本篇主要演示显示中文 在arduino中有一个u8g2库 当时用UNO时使用过该库 但是用在ESP32上
  • ESP32-S2应用开发——USB通信(CDC类)

    ESP32S2应用开发 USB通信 CDC类 目录 ESP32S2应用开发 USB通信 CDC类 前言 1 硬件介绍 1 1 硬件连接 2 软件开发 2 1 安装开发板 2 2 安装库 2 3 运行示例代码 2 4 USB传输速度测试 结束
  • 全网最简洁的mpy-cross教程

    大家知道我一向精干 不喜欢搞花儿的 如果去mpy官网看mpy cross的相关资料 估计又得绕蒙 跟我来 保证你三分钟学会 但是本文不涉及原理 第一 mpy cross是干嘛滴 答 把py文件转成mpy系统读的mpy文件 术语咱不懂 叫交叉
  • 在 esp32 上运行 lvgl + freetype

    前言 最近有个需求 如何在 esp32 上运行 lvgl freetype 这个想法的难点是 freetype 的环境搭建 我想将其做得非常简单 最好的办法是做成组件来使用 所以我将 freetype 的相关依赖做成了 esp idf 组件
  • 还是 “月饼” 后续,玩转炫彩 “月饼” 之 问题说明

    画一个 月饼 陪我过中秋 开发板后续问题跟进说明 目录 前言 一 出现问题 二 寻求办法 三 若有所思 四 问题测试 结语 悬赏送开发板 前言 本文有纯理论玩家是永远不会经历的实际问题 嵌入式工程师不动手永远出不了作品 本文最后有送开发板的
  • 最快方式 ESP-IDF 创建例子 教程

    需要条件 安装了 VSCODE 安装了插件 Espressif IDF工具 系统中安装了 ESP IDF 可使用离线包 或在线安装包 在插件中配置了 ESP IDF 可能需要在线更新一些东西 点击F1 输入 ESP 等待提示 出现提示后 选
  • cmake中的编译选项

    CMake是一个跨平台的构建系统 它可以根据简单的配置文件生成各种平台的构建工具 例如Makefile Visual Studio项目文件等 CMake使用CMakeLists txt文件来描述项目的构建规则和依赖关系 在这个文件中 可以设
  • 【自用】无法通过ESP32创建HomeAssistant实体问题解决(MQTT对ESP32创建实体请求无应答)

    一 问题描述 1 使用 MQTTX 测试客户端能够创建实体 当通过 MQTTX 发送注册实体请求的时候 实体能够在 MQTT 服务器中注册成功 2 使用 ESP32 无法创建实体 在ESP32中通过 publish 函数发送注册请求的时候
  • 如何将后台工作线程设置为单线程单元?

    我正在创建一个自动化测试运行应用程序 在应用程序的这一部分中 我正在开发一个轮询服务器 它的工作原理是不断轮询 Web 服务器以确定何时应运行新的自动化测试 对于我们的 GUI 应用程序的夜间自动运行 当轮询服务器看到请求时 它会下载所有必
  • 【ESP32接入国产大模型之文心一言】

    1 怎样接入文心一言 随着人工智能技术的不断发展 自然语言处理领域也得到了广泛的关注和应用 在这个领域中 文心一言作为一款强大的自然语言处理工具 具有许多重要的应用价值 本文将重点介绍如何通过ESP32接入国产大模型之文心一言api 以及其

随机推荐

  • MySQL常见用法

    文章目录 一 时间类1 1 DATE SUB 函数1 2 NOW CURDATE CURTIME DATE 函数1 3 实战 二 统计类三 字符类3 1 LOCATE 函数3 2 concat 函数3 3 concat ws 函数3 4 g
  • 牢记公式,ardupilot EKF2就是纸老虎(四)!

    版权声明 xff1a 本文为博主原创文章 xff0c 转载请附上博文链接 xff01 四 一睹EKF2芳容 因为篇幅过长 xff0c 写的一些公式会乱码 xff0c 没办法只能把 牢记公式 xff0c ardupilot EKF2就是纸老虎
  • Java Optional使用

    文章目录 Optional一 Optional 简介二 创建 Optional 实例2 1 empty 方法2 2 of 方法2 3 ofNullable 方法 三 Optional的使用3 1 访问 Optional 对象的值3 1 1
  • 正则表达式:基础详解以及在Java中的使用

    文章目录 一 正则表达式1 1 正则表达式中的特殊字符1 2 正则表达式所支持的合法字符1 3 方括号表达式1 4 边界匹配符1 5 三种模式的数量表示符 二 应用2 1 String 类2 2 Pattern 类和 Matcher 类 一
  • Python学习:关键字global和nonlocal的用法说明

    一 global global关键字用来在函数或其他局部作用域中使用全局变量 1 1 如果局部要对全局变量修改 xff0c 而不使用global关键字 count 61 0 def global test count 43 61 1 pri
  • Python:flask框架下前后端的数据交互

    文章目录 前提 一 前端发送数据 xff0c 后端接受数据1 1 路由传参数 数据 1 2 表单提交 二 后端发送数据 xff0c 前端接受数据 前提 后端 xff1a python 的 flask 框架 前端 xff1a html css
  • Python关于None的报错:'NoneType' object is not iterable和cannot unpack non-iterable NoneType object

    文章目录 一 TypeError 39 NoneType 39 object is not iterable xff08 类型错误 xff1a 39 NoneType 39 对象不是可迭代的 xff09 二 TypeError cannot
  • Git:合并分支----git merge命令应用的三种情景

    文章目录 一 git merge 命令应用的三种情景1 1 快进 无冲突 1 2 非 快进 xff0c 修改不同文件 无冲突 1 3 非 快进 xff0c 修改相同文件 有冲突 一 git merge 命令应用的三种情景 1 1 快进 无冲
  • Git:远程分支----git fetch命令的使用

    git fetch 命令的使用 从远程主机克隆 Git 的 clone 命令会为你自动将远程主机命名为 origin xff0c 拉取它的所有数据 xff0c 创建一个指向它的 master 分支的指针 xff0c 并且在本地将其命名为 o
  • Git:移除文件----git rm命令的使用

    文章目录 一 git rm 命令使用1 1 rm 命令1 2 git rm 命令1 3 git rm f 命令1 4 git rm cached 命令 一 git rm 命令使用 Git 本地数据管理 xff0c 大概可以分为三个区 xff
  • 【OpenMv小车】OpenMv追小球的小车之pid调用

    pid py gt gt https github com wagnerc4 flight controller blob master pid py openmv 官网 xff1a http book openmv cc project
  • 【深入理解C++】函数模板作为成员函数

    文章目录 1 普通类的成员函数模板2 类模板的成员函数模板 1 普通类的成员函数模板 不管是普通类还是类模板 xff0c 它们的成员函数都可以是函数模板 xff0c 称为成员函数模板 xff0c 但不可以是虚函数 xff0c 否则编译器报错
  • QGroundControl开发之使用自定义mavlink

    工具 对QGC进行二次开发时 xff0c 常常会遇到想使用自定义mavlink的情况 xff0c 但不像APM那样编译命令会根据xml文件自动生成mavlink协议 QGC似乎不能自动生成mavlink协议 xff08 之前试过似乎不能自动
  • 字符串连接 (c语言)

    题目描述 将给定的字符串连接起来 书中的算法描述如下 xff1a 图 xff1a 字符串连接算法 输入描述 三对字符串 xff0c 每对字符串占一行 xff0c 用空格隔开 每个字符串只包含数字和英文字母大小写且长度不超过100 输出描述
  • STM32—UART中断收发 Day4

    软件 xff1a STM32CubeMX xff0c MDK ARM 硬件 xff1a 蓝桥杯物联网Lora开发板 xff0c 板载芯片STM32L071 一 STM32CubeMX配置 1 先在连接 xff08 Connectivity
  • 虚拟机出现command XXX is available in /bin/ls问题

    问题 xff1a 使用本地的shell命令时候 The command could not be located because 39 usr bin bin 39 is not included in the PATH environme
  • 全志lichee的pack命令

    全志lichee目录打包命令流程 pack 将打包命令传进去build sh脚本里面 查看buildsh里面的脚本命令 其实里面的脚本还是较为简单地的 xff0c 仅仅是作为一个过渡 xff0c 然后就跑进去buildroot script
  • Linux_kernel驱动之GPIO子系统

    前言 xff1a gpio子系统的内容在drivers gpio文件夹下 xff0c 主要文件有 xff1a devres c xff1a devres c是针对gpio api增加的devres机制的支持gpiolib c xff1a g
  • 转载:全志问题解决方法

    版权声明 xff1a 本文为博主原创文章 xff0c 遵循 CC 4 0 BY SA 版权协议 xff0c 转载请附上原文出处链接和本声明 本文链接 xff1a https blog csdn net yanzheng1113 articl
  • 从零开始学ESP32:(二) 开启ESP32WIFI -STA和AP模式共存

    从零开始学ESP32 xff1a 个人笔记记录 xff1a 芯片型号 ESP32 网络环境支持 LWIP IDF PY SDK ESP IDF v4 3 芯片功能 xff1a 支持STA AP网络共存模式 xff1a 工程 xff1a es