首页 > 其他分享 >【保姆级IDF】ESP32最强WIFI模式:AP+STA,实现数据中继站

【保姆级IDF】ESP32最强WIFI模式:AP+STA,实现数据中继站

时间:2024-10-27 12:47:22浏览次数:3  
标签:sta ESP ESP32 WIFI AP event STA

Tips:

抛砖引玉,本文记录ESP32学习过程中遇到的收获。如有不对的地方,欢迎指正。

目录

1. 前言

2. 实现功能

3. 代码思路

4. 代码讲解

4.1 AP_STA模式下的其他设备接入以及接入其他设备的WIFI配置

4.2 自身作为AP和STA模式下的不同套接字的创建及配置

4.3 WIFI事件中被调用的接受连接函数

4.4 数据转发实现中继功能函数

4.5 主函数初始化WIFI外设

4.6 头文件以及宏定义等内容

5.成果展示

6. 总结


1. 前言

        本文介绍ESP32使用WIFI时设置为AP+STA模式下的数据通信,并进行两台设备的数据中继服务。WIFI的基础知识以及ESP32用作单一STA模式或AP模式的文章在我的专栏中可以查看到,这里顺便贴上链接:

【保姆级IDF】ESP32使用WIFI作为AP模式TCP通信:连接客户端+一对多通信

【保姆级IDF】ESP32使用WIFI作为STA模式与其他设备进行TCP通信

【保姆级IDF】ESP32使用WIFI作为STA模式实现:WIFI扫描串口输出+串口输入指定WIFI名称和密码+连接WIFI

读者自行查阅即可,不再赘述。

博主使用合宙的ESP32C3开发板:

2. 实现功能

        1.可作为WIFI热点被其他设备连接并进行通信,可作为STA站点连接其他设备并进行通信

        2.连接到自身的两台设备可以互传数据,自身作为STA站点所连接到的AP热点发来的消息可以广播给所有连接到自身热点的STA站点设备

        3.实现数据中继站的功能,将一台设备发送数据发送到另一台设备,两台设备数据可互相转发

3. 代码思路

        1.使用ESP32的示例代码:softap_sta为基础开发,该代码提供让设备接入自身热点以及让自身连接其他热点的功能。

        2.当自身作为STA模式时,连接上其它WIFI后配置套接字,建立TCP连接。

        3.当自身作为AP模式时,创建套接字并进行相关配置,在WIFI事件:设备接入事件中进行accept接受连接。

        4.创建一个任务去处理两台设备发来的数据。

4. 代码讲解

4.1 AP_STA模式下的其他设备接入以及接入其他设备的WIFI配置

static void wifi_event_handler(void *arg, esp_event_base_t event_base,
                               int32_t event_id, void *event_data)
{
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STACONNECTED) {
        wifi_event_ap_staconnected_t *event = (wifi_event_ap_staconnected_t *) event_data;
        ESP_LOGI(TAG_AP, "Station "MACSTR" joined, AID=%d",
                 MAC2STR(event->mac), event->aid);
        for (int i = 0; i < 10; i++)//遍历连接设备信息表,查找是否有空闲位置
    {
        if (sta_info[i].sock == 0)//如果查找到空闲位置
        {
            memcpy(sta_info[i].deveci_mac, event->mac, 6);//将接入设备的mac地址存入连接设备信息表
            accept_func(ap_scok,i,(void *)&sta_info);//将连接成功后返回的套接字描述符存入连接设备信息表
            break;
        }
    } 
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STADISCONNECTED) {
        wifi_event_ap_stadisconnected_t *event = (wifi_event_ap_stadisconnected_t *) event_data;
        ESP_LOGI(TAG_AP, "Station "MACSTR" left, AID=%d",
                 MAC2STR(event->mac), event->aid);
        for(int j = 0; j < 10; j++)//遍历连接设备信息表,查找退出设备的mac地址
        {
            if(strcmp((char*)sta_info[j].deveci_mac, (char*)event->mac) == 0)//如果找到则:
            {
                shutdown(sta_info[j].sock, 0);//停止连接设备的套接字描述符的工作
                close(sta_info[j].sock);//关闭连接设备的套接字描述符
                sta_info[j].sock = 0;//将连接设备信息表中的套接字描述符置为0
                memset(&sta_info[j].deveci_mac, 0, sizeof(sta_info[j].deveci_mac));//将连接设备信息表中的mac地址置为0
                break;
            }
        }
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
        esp_wifi_connect();//连接wifi
        ESP_LOGI(TAG_STA, "Station started");
    } 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_STA, "Got IP:" IPSTR, IP2STR(&event->ip_info.ip));
        s_retry_num = 0;
        xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);//设置wifi连接标志位
    }
}


esp_netif_t *wifi_init_softap(void)
{
    esp_netif_t *esp_netif_ap = esp_netif_create_default_wifi_ap();//创建默认的wifi热点

    wifi_config_t wifi_ap_config = {
        .ap = {
            .ssid = EXAMPLE_ESP_WIFI_AP_SSID,
            .ssid_len = strlen(EXAMPLE_ESP_WIFI_AP_SSID),
            .channel = EXAMPLE_ESP_WIFI_CHANNEL,
            .password = EXAMPLE_ESP_WIFI_AP_PASSWD,
            .max_connection = EXAMPLE_MAX_STA_CONN,
            .authmode = WIFI_AUTH_WPA2_PSK,
            .pmf_cfg = {
                .required = false,
            },
        },
    };

    if (strlen(EXAMPLE_ESP_WIFI_AP_PASSWD) == 0) {
        wifi_ap_config.ap.authmode = WIFI_AUTH_OPEN;
    }

    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_ap_config));

    ESP_LOGI(TAG_AP, "wifi_init_softap finished. SSID:%s password:%s channel:%d",
             EXAMPLE_ESP_WIFI_AP_SSID, EXAMPLE_ESP_WIFI_AP_PASSWD, EXAMPLE_ESP_WIFI_CHANNEL);

    return esp_netif_ap;
}

esp_netif_t *wifi_init_sta(void)
{
    esp_netif_t *esp_netif_sta = esp_netif_create_default_wifi_sta();//创建默认的wifi站

    wifi_config_t wifi_sta_config = {
        .sta = {
            .ssid = EXAMPLE_ESP_WIFI_STA_SSID,
            .password = EXAMPLE_ESP_WIFI_STA_PASSWD,
            .scan_method = WIFI_ALL_CHANNEL_SCAN,
            .failure_retry_cnt = EXAMPLE_ESP_MAXIMUM_RETRY,
            /* Authmode threshold resets to WPA2 as default if password matches WPA2 standards (pasword len => 8).
             * If you want to connect the device to deprecated WEP/WPA networks, Please set the threshold value
             * to WIFI_AUTH_WEP/WIFI_AUTH_WPA_PSK and set the password with length and format matching to
            * WIFI_AUTH_WEP/WIFI_AUTH_WPA_PSK standards.
             */
            .threshold.authmode = ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD,
            .sae_pwe_h2e = WPA3_SAE_PWE_BOTH,
        },
    };

    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_sta_config) );

    ESP_LOGI(TAG_STA, "wifi_init_sta finished.");

    return esp_netif_sta;
}

        WIFI的两种模式的配置,等待主函数的调用。代码中有注释,且是例程代码,很好理解。

4.2 自身作为AP和STA模式下的不同套接字的创建及配置

uint8_t sta_socket_init(void)//自身作为STA模式时套接字的创建以及配置
{
    /* 初始化sta套接字 */
    sta_scok = socket(AF_INET, SOCK_STREAM, 0);
    if (sta_scok < 0) {//若返回值为-1,则表示创建失败,处理创建失败的情况
        perror("STA_socket");//以socket:形式输出错误信息
        exit(EXIT_FAILURE);//退出
        return -1; //返回-1
    }
    struct sockaddr_in server_addr;//创建一个结构体变量,用于存储服务器的地址信息
    memset(&server_addr, 0, sizeof(server_addr));//清零初始化
    server_addr.sin_family = AF_INET;//设置地址族为IPv4
    server_addr.sin_port = htons(8080);//设置端口号为8080
    inet_pton(AF_INET, "替换成要连接的目标IP地址如:192.168.0.0", &server_addr.sin_addr); //将点分十进制的IP地址转换为二进制形式,并存储在server_addr.sin_addr中
 
    if (connect(sta_scok, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {//连接服务器
        perror("connect");//以connect:形式输出错误信息
        closesocket(sta_scok);//关闭套接字
        exit(EXIT_FAILURE);//退出
        vTaskDelay(6 / portTICK_PERIOD_MS);//延时6ms
    }
    return 0;
}

uint8_t ap_socket_init(void)//自身作为AP模式时套接字的创建以及配置
{
    struct sockaddr_storage dest_addr;//目标地址
    struct sockaddr_in *dest_addr_ip4 = (struct sockaddr_in *)&dest_addr;//IPv4地址
        dest_addr_ip4->sin_addr.s_addr = htonl(INADDR_ANY);//设置本机IP
        dest_addr_ip4->sin_family = AF_INET;//IPv4协议
        dest_addr_ip4->sin_port = htons(PORT);//端口号
    /* 初始化ap套接字 */
    ap_scok = socket(AF_INET, SOCK_STREAM, 0);
    if (ap_scok < 0) {//创建失败
        ESP_LOGE(TAG_AP, "Unable to create socket: errno %d", errno);//打印错误信息
        vTaskDelete(NULL);//删除任务
        return -1;
    }
    int opt = 1;
    setsockopt(ap_scok, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));//设置socket选项为:可重用
#if defined(CONFIG_EXAMPLE_IPV4) && defined(CONFIG_EXAMPLE_IPV6)
    // Note that by default IPV6 binds to both protocols, it is must be disabled
    // if both protocols used at the same time (used in CI)
    setsockopt(listen_sock, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));
#endif
    ESP_LOGI(TAG_AP, "Socket created");
    int err = bind(ap_scok, (struct sockaddr *)&dest_addr, sizeof(dest_addr));//绑定socket到目标地址
    if (err != 0) {//绑定失败
        ESP_LOGE(TAG_AP, "Socket unable to bind: errno %d", errno);//打印错误信息
        //ESP_LOGE(TAG_AP, "IPPROTO: %d", addr_family);//打印IP协议
        goto CLEAN_UP;//跳转到CLEAN_UP标签处
    }
    ESP_LOGI(TAG_AP, "Socket bound, port %d", PORT);
 
    err = listen(ap_scok, 3);//监听socket
    if (err != 0) {//监听失败
        ESP_LOGE(TAG_AP, "Error occurred during listen: errno %d", errno);//打印错误信息
        goto CLEAN_UP;//跳转到CLEAN_UP标签处
    }
    ESP_LOGI(TAG_AP, "Socket listening");
    return 0;
CLEAN_UP:
    close(ap_scok);//关闭socket
    return -1;
}

        根据不同模式下的工作特性,进行合理的配置。该部分内容许多都在前几期文章中详细解释过,读者可借助代码中的注释进行理解。

4.3 WIFI事件中被调用的接受连接函数

void accept_func(int sockfd,int offset,STA_info *sta_info)
{
    int keepAlive = 1;
    int keepIdle = KEEPALIVE_IDLE;
    int keepInterval = KEEPALIVE_INTERVAL;
    int keepCount = KEEPALIVE_COUNT;
    socklen_t addr_len = sizeof(sta_info[offset].source_addr);
    sta_info[offset].sock = accept(sockfd, (struct sockaddr *)&sta_info[offset].source_addr, &addr_len);//接受连接
    if (sta_info[offset].sock < 0) {
        ESP_LOGE(TAG_AP, "Unable to accept connection: errno %d", errno);
    }
        // Set tcp keepalive option
        setsockopt(sta_info[offset].sock, SOL_SOCKET, SO_KEEPALIVE, &keepAlive, sizeof(int));//设置socket选项为:保持连接
        setsockopt(sta_info[offset].sock, IPPROTO_TCP, TCP_KEEPIDLE, &keepIdle, sizeof(int));//设置socket选项为:保持空闲时间
        setsockopt(sta_info[offset].sock, IPPROTO_TCP, TCP_KEEPINTVL, &keepInterval, sizeof(int));//设置socket选项为:保持间隔时间
        setsockopt(sta_info[offset].sock, IPPROTO_TCP, TCP_KEEPCNT, &keepCount, sizeof(int));//设置socket选项为:保持计数
}

        只有在有设备接入自己时才去接受连接,accept会有阻塞,所以需要在连接到ESP32的设备立即建立TCP连接。

4.4 数据转发实现中继功能函数

static void Task_do_retransmit()
{
    int AP_len;//ap接收到的数据长度
    int STA_len;//sta接收到的数据长度
    char rx_buffer[128];//接收缓冲区
    while(1){
        vTaskDelay(200 / portTICK_PERIOD_MS);//延时200ms
        STA_len = recv(sta_scok, rx_buffer, sizeof(rx_buffer) - 1, 0x08);//接收数据
        if(STA_len > 0){//如果接收到了数据,注意WIFi接收到的数据是小端模式,我们单片机内存使用大端模式存储数据!
            for(int x = 0; x < 10; x++){//遍历所有连接的设备
            if(sta_info[x].sock != 0){//如果设备已连接
                send(sta_info[x].sock, rx_buffer, STA_len, 0);//转发数据
            }
        }
        }
        for(int k = 0; k < 10; k++){//遍历所有连接的设备
            if(sta_info[k].sock != 0){//如果设备已连接
                AP_len = recv(sta_info[k].sock, rx_buffer, sizeof(rx_buffer) - 1, 0x08);//接收数据
                if(AP_len > 0){//如果接收到了数据
                    int err = send(sta_scok, rx_buffer, AP_len, 0);//转发数据
                    printf("\nrecvive data: %s\n",rx_buffer);//打印接收到的数据
                    if(err > 0)
                    printf("\ntransaction OK!\n");//完成转发
                }
            }
        }
    }
}

        代码注释解释的很清楚了,唯一要注意的是WIFI发送数据过程中使用的是小端模式,而在单片机内存中一般都以大端模式存储。说白了就是你想发0x1234,那么WIFI转发出去后设备收到的是0x3412(以1字节对齐的情况下)。这里我们没有对大小端模式进行处理,因为中继数据需要转发,ESP32第一次收到WIFI数据后原封不动的转发成WIFI数据,那么数据的存储方式就会这样转化:

        当收到其他设备的数据后,这个数据在缓冲区以小端模式存放,ESP32并不做处理,原封不动的在用WIFI转发给其他设备之后,WIFI又会再一次的将这个"小端模式数据"作处理,在进行一次小端模式的发送。此时这个数据会被转化为大端模式,WIFI发送数据方式并不会准确识别数据的意义,而是直接进行字节序的转换。所以在经过WIFI两次发送之后,原本的大端模式数据会被小端模式倒两次变回最原本的数据。

4.5 主函数初始化WIFI外设

void app_main(void)
{
    ESP_ERROR_CHECK(esp_netif_init());//初始化网络接口
    ESP_ERROR_CHECK(esp_event_loop_create_default());//创建事件循环

    //Initialize NVS
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        ESP_ERROR_CHECK(nvs_flash_erase());//清除flash
        ret = nvs_flash_init();//重新初始化
    }
    ESP_ERROR_CHECK(ret);

    /* Initialize event group */
    s_wifi_event_group = xEventGroupCreate();//创建事件组

    /* Register Event handler */
    ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,//注册WIFI事件
                    ESP_EVENT_ANY_ID,
                    &wifi_event_handler,
                    NULL,
                    NULL));
    ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,//注册IP事件
                    IP_EVENT_STA_GOT_IP,
                    &wifi_event_handler,
                    NULL,
                    NULL));

    /*Initialize WiFi */
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));//初始化wifi

    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_APSTA));//设置wifi模式为AP+STA

    /* Initialize AP */
    ESP_LOGI(TAG_AP, "ESP_WIFI_MODE_AP");
    esp_netif_t *esp_netif_ap = wifi_init_softap();//初始化AP

    /* Initialize STA */
    ESP_LOGI(TAG_STA, "ESP_WIFI_MODE_STA");
    esp_netif_t *esp_netif_sta = wifi_init_sta();//初始化STA

    /* Start WiFi */
    ESP_ERROR_CHECK(esp_wifi_start() );//启动wifi

    /*
     * Wait until either the connection is established (WIFI_CONNECTED_BIT) or
     * connection failed for the maximum number of re-tries (WIFI_FAIL_BIT).
     * The bits are set by event_handler() (see above)
     */
    EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,//等待事件组
                                           WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
                                           pdFALSE,
                                           pdFALSE,
                                           portMAX_DELAY);

    /* xEventGroupWaitBits() returns the bits before the call returned,
     * hence we can test which event actually happened. */
    if (bits & WIFI_CONNECTED_BIT) {
        ESP_LOGI(TAG_STA, "connected to ap SSID:%s password:%s",
                 EXAMPLE_ESP_WIFI_STA_SSID, EXAMPLE_ESP_WIFI_STA_PASSWD);
    } else if (bits & WIFI_FAIL_BIT) {
        ESP_LOGI(TAG_STA, "Failed to connect to SSID:%s, password:%s",
                 EXAMPLE_ESP_WIFI_STA_SSID, EXAMPLE_ESP_WIFI_STA_PASSWD);
    } else {
        ESP_LOGE(TAG_STA, "UNEXPECTED EVENT");
        return;
    }

    /* Set sta as the default interface */
    esp_netif_set_default_netif(esp_netif_sta);//设置sta为默认接口

    /* Enable napt on the AP netif */
    if (esp_netif_napt_enable(esp_netif_ap) != ESP_OK) {//启用NAPT
        ESP_LOGE(TAG_STA, "NAPT not enabled on the netif: %p", esp_netif_ap);
    }
    sta_socket_init();//初始化作为sta模式的套接字
    ap_socket_init();//初始化作为ap模式套接字
    xTaskCreate(Task_do_retransmit, "Task_do_retransmit", 4096 * 5, NULL, 5, NULL);//创建任务
}

4.6 头文件以及宏定义等内容

#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_mac.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_netif_net_stack.h"
#include "esp_netif.h"
#include "nvs_flash.h"
#include "lwip/inet.h"
#include "lwip/netdb.h"
#include "lwip/sockets.h"
#include "lwip/lwip_napt.h"
#include "lwip/err.h"
#include "lwip/sys.h"

#define EXAMPLE_ESP_WIFI_STA_SSID           "替换成要连接的热点名称"//CONFIG_ESP_WIFI_REMOTE_AP_SSID
#define EXAMPLE_ESP_WIFI_STA_PASSWD         "替换成要连接的热点密码"//CONFIG_ESP_WIFI_REMOTE_AP_PASSWORD
#define EXAMPLE_ESP_MAXIMUM_RETRY           CONFIG_ESP_MAXIMUM_STA_RETRY

#if CONFIG_ESP_WIFI_AUTH_OPEN
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD   WIFI_AUTH_OPEN
#elif CONFIG_ESP_WIFI_AUTH_WEP
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD   WIFI_AUTH_WEP
#elif CONFIG_ESP_WIFI_AUTH_WPA_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD   WIFI_AUTH_WPA_PSK
#elif CONFIG_ESP_WIFI_AUTH_WPA2_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD   WIFI_AUTH_WPA2_PSK
#elif CONFIG_ESP_WIFI_AUTH_WPA_WPA2_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD   WIFI_AUTH_WPA_WPA2_PSK
#elif CONFIG_ESP_WIFI_AUTH_WPA3_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD   WIFI_AUTH_WPA3_PSK
#elif CONFIG_ESP_WIFI_AUTH_WPA2_WPA3_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD   WIFI_AUTH_WPA2_WPA3_PSK
#elif CONFIG_ESP_WIFI_AUTH_WAPI_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD   WIFI_AUTH_WAPI_PSK
#endif

/* AP Configuration */
#define EXAMPLE_ESP_WIFI_AP_SSID            "创建的热点名称"//CONFIG_ESP_WIFI_AP_SSID
#define EXAMPLE_ESP_WIFI_AP_PASSWD          "创建的热点密码"//CONFIG_ESP_WIFI_AP_PASSWORD
#define EXAMPLE_ESP_WIFI_CHANNEL            CONFIG_ESP_WIFI_AP_CHANNEL
#define EXAMPLE_MAX_STA_CONN                CONFIG_ESP_MAX_STA_CONN_AP


/* The event group allows multiple bits for each event, but we only care about two events:
 * - we are connected to the AP with an IP
 * - we failed to connect after the maximum amount of retries */
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT      BIT1

static const char *TAG_AP = "WiFi SoftAP";
static const char *TAG_STA = "WiFi Sta";

static int s_retry_num = 0;

/* FreeRTOS event group to signal when we are connected/disconnected */
static EventGroupHandle_t s_wifi_event_group;
int sta_scok = 0;//STA模式的套接字
int ap_scok = 0;//AP模式的套接字

typedef struct _STA_info{
    uint8_t deveci_mac[6];//连接设备的mac地址
    int sock;             //连接设备的套接字描述符
    struct sockaddr_storage source_addr;//存储连接设备的IP与端口号
}STA_info;
void accept_func(int sockfd,int offset,STA_info *sta_info);
STA_info sta_info[10];//连接设备信息表
#define PORT                        3333
#define KEEPALIVE_IDLE              5
#define KEEPALIVE_INTERVAL          5
#define KEEPALIVE_COUNT             3
#define INADDR_ANY          ((u32_t)0xc0a80401UL)//192.168.4.1

        以上函数中没找到的变量和宏都可以在这里找到。

5.成果展示

        电脑端作为ESP32所连接的热点,收到手机发来的数据,这个数据由ESP32中继转发出来,电脑也可以向手机发送数据,ESP32同样可以中继转发。

6. 总结

        以上就是本次分享的全部内容,用好AP+STA模式的基础是将单独的AP和STA模式使用好,包括WIFI的配置和套接字的熟练使用,融会贯通之后便会更上一层楼。如何使用AP模式、STA模式在前几期文章中有详细解释。链接放在上文了。大家如果觉得有用,还请多多点赞收藏关注,你的支持就是对我最大的肯定。谢谢大家!

        如有疑问欢迎留言交流。以上观点为个人理解,只想抛砖引玉,如有不对的地方欢迎指正,不喜轻喷。


2024.10.26-18:00

WIFI部分完结撒花,下期开始更新蓝牙的使用

标签:sta,ESP,ESP32,WIFI,AP,event,STA
From: https://blog.csdn.net/zyZYzy9900/article/details/143256473

相关文章

  • 【Motion Forecasting】SmartRefine:A Scenario-Adaptive Refinement Framework for Mo
    SmartRefine:AScenario-AdaptiveRefinementFrameworkforEfficientMotionPrediction今天要分享的文章来自于商汤科技在CVPR2024发表的文章SmartRefine,这是一项关注于双阶段轨迹解码器的改进工作。Abstract预测自动驾驶车辆周围智能体的未来运动对于自动驾驶车辆......
  • C++ -stack、queue
    博客主页:【夜泉_ly】本文专栏:【C++】欢迎点赞......
  • 通过duxapp提供的基础方法、UI组件、全局样式,快速编写项目
    使用duxapp,我是如何实现快速完成项目开发的?像下面这个例子,这个项目有140多个页面,但是真实的开发时间,在熟练使用duxapp的情况下,不会超过两周,并且可以将它兼容APP、小程序、H5这里仅展示了其中一部分页面,这个项目主要包含下这些功能购物订单流程售后退换文章发布门店功能......
  • 面向对象高级-static
    文章目录1.1static修饰成员变量1.2static修饰成员变量的应用场景1.3static修饰成员方法1.4工具类来看static的应用1.5static的注意事项1.6static应用(代码块)1.7static应用(单例设计模式)static读作静态,可以用来修饰成员变量,也能修饰成员方法。1.1st......
  • Stable Diffusion 3.5 正式发布!免费开源,堪称最强AI文生图模型,附本地安装和在线使用教
    关键要点:10月22日,stability.ai重磅推出StableDiffusion3.5,号称迄今为止最强大的文生图模型。此次公开版本包括多个模型变体,其中有StableDiffusion3.5Large和StableDiffusion3.5LargeTurbo。此外,StableDiffusion3.5Medium将于10月29日发布。这些模型在尺......
  • kube-prometheus-stack 自定义 alertmanager 配置推送webhook
    创建AlertmanagerConfig资源在没有使用prometheus-operator的情况下,需要手动配置alertmanager.yaml来路由&发送从prometheus接收的警报。使用prometheus-operator之后,事情变得简单一些。只需要创建AlertmanagerConfig资源,prometheus-operator会自动merge所有的Ale......
  • zlibrary镜像网页,zlibrary电脑客户端/app下载
    Z-Library,也被广泛称为BookFinder.Z-Library或简称Z-Lib,是一个广受欢迎的数字图书馆和电子书存储库。以下是对Z-Library的详细介绍:一、概述Z-Library自称为“世界上最大的免费电子书库”,拥有超过10,000,000+本电子书和84,000,000+篇文章供用户免费下载。这些资源涵盖了广泛的学......
  • js调用datasnap rest server
    场景:有嵌套的多层json数据结构的变量,js通过post调用datasnaprestserver,会出现问题:varjson=[{stcd:system.sn,dateTime:dateTimeStr,stnm:system.stnm,lgtd:system.lgtd,lttd:system.lttd,stlc:system.stlc,mydata:{"test_key":"test_value......
  • 化学仿真软件:ChemCAD二次开发_ChemCAD编程语言与API使用
    ChemCAD编程语言与API使用在ChemCAD中进行二次开发,需要熟悉其编程语言和API(应用程序编程接口)。ChemCAD的编程语言是基于C++的,通过API可以访问和控制ChemCAD的各个功能模块。本节将详细介绍如何使用ChemCAD的API进行编程,包括基本概念、常用API函数、数据处理方法以及实际操......
  • stamina 生产级的python 重试包
    stamina是基于tenacity的包装包含的特性仅对某些异常(甚至是其中的子集)进行重试,方法是先使用谓词进行自检重试之间带有抖动的指数退避限制重试次数和总时间自动异步支持-包括Trio保留所装饰的可调用函数的类型提示开箱即用,可灵活使用Prometheus、structlog和标准库的支持l......