深入分析 ESP32 的 WiFi 状态机

2023-11-07

本工程已托管到 GitHub,具体路径是 https://github.com/tidyjiang8/esp32-projects/tree/master/sta


在前一篇博客 【让 ESP32 连接到你的 WiFi 热点】 中,我们已经简单地分析了一下 WiFi 的工作流程,并简要提示了一下事件调度器/WiFi 状态机,我们将在这一篇博客中详细分析。

在 ESP-IDF 中,整个 wifi 协议栈是一个状态机,它在各个时刻都有一个状态。用户可以根据自己的需要,让协议栈在运行到某个状态时自动处理某些工作。理解清楚整个 WiFi 状态机有利于我们编写出更好的应用程序,其中最最基础的功能就是【断网重连】,这在我们的 sta 项目中已经实现了,请参考该源码。

【协议栈的状态定义】

在 ESP-IDF 中,整个网络协议栈包含的状态定义在头文件 components/esp32/include/esp_event.h中,由枚举类型 system_event_id_t 定义:

typedef enum {
    SYSTEM_EVENT_WIFI_READY = 0,           /**< ESP32 WiFi 准备就绪*/
    SYSTEM_EVENT_SCAN_DONE,                /**< ESP32 完成扫描 AP */
    SYSTEM_EVENT_STA_START,                /**< ESP32 sta 启动 */
    SYSTEM_EVENT_STA_STOP,                 /**< ESP32 sta 停止 */
    SYSTEM_EVENT_STA_CONNECTED,            /**< ESP32 sta 连接到 AP */
    SYSTEM_EVENT_STA_DISCONNECTED,         /**< ESP32 sta 从 AP 断开连接 */
    SYSTEM_EVENT_STA_AUTHMODE_CHANGE,      /**< ESP32 sta 所连接的 AP 的授权模式改变了 */
    SYSTEM_EVENT_STA_GOT_IP,               /**< ESP32 sta 从 AP 获取到 IP 地址 */
    SYSTEM_EVENT_STA_WPS_ER_SUCCESS,       /**< ESP32 sta wps succeeds in enrollee mode */
    SYSTEM_EVENT_STA_WPS_ER_FAILED,        /**< ESP32 sta wps fails in enrollee mode */
    SYSTEM_EVENT_STA_WPS_ER_TIMEOUT,       /**< ESP32 sta wps timeout in enrollee mode */
    SYSTEM_EVENT_STA_WPS_ER_PIN,           /**< ESP32 sta wps pin code in enrollee mode */
    SYSTEM_EVENT_AP_START,                 /**< ESP32 soft-AP 启动*/
    SYSTEM_EVENT_AP_STOP,                  /**< ESP32 soft-AP 停止*/
    SYSTEM_EVENT_AP_STACONNECTED,          /**< 有 sta 连接到 ESP32 soft-AP */
    SYSTEM_EVENT_AP_STADISCONNECTED,       /**< 有 sta 从 ESP32 soft-AP 断开连接 */
    SYSTEM_EVENT_AP_PROBEREQRECVED,        /**< soft-AP 接口接收到探测请求报文*/
    SYSTEM_EVENT_AP_STA_GOT_IP6,           /**< ESP32 sta/ap 接口获取到 IPv6 地址 */
    SYSTEM_EVENT_ETH_START,                /**< ESP32 ethernet start */
    SYSTEM_EVENT_ETH_STOP,                 /**< ESP32 ethernet stop */
    SYSTEM_EVENT_ETH_CONNECTED,            /**< ESP32 ethernet phy link up */
    SYSTEM_EVENT_ETH_DISCONNECTED,         /**< ESP32 ethernet phy link down */
    SYSTEM_EVENT_ETH_GOT_IP,               /**< ESP32 ethernet got IP from connected AP */
    SYSTEM_EVENT_MAX
} system_event_id_t;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

【查看 ESP32 连接到 AP 时经历的各个状态】

ESP32 的日志默认级别是 INFO,即只有级别大于等于 INFO 级别的消息才会被打印到串口上,我们要查看 WiFi 连接过程中的各个状态,需要修改日志的打印级别,这是在配置菜单中完成的。

运行命令 make menuconfig 进入图形化配置菜单,然后依次选择 Component config --->Log output --->Default log verbosity (Info) --->,然后选择打印级别为Debug

这里写图片描述

日志的最低级别明明是 Verbose,我们选择的级别为啥是 Debug?请继续看后续分解^_^

选择好日志级别后,退出配置界面,保存配置,然后执行命令make flash monitor重新编译、烧写程序并查看串口输出。

为了避免文章太过冗长,下面截取了一部分与 WiFi 相关的日志:

I (720) wifi: Init dynamic tx buffer num: 32
I (720) wifi: Init dynamic rx buffer num: 64
I (720) wifi: wifi driver task: 3ffbd668, prio:23, stack:3584
I (730) wifi: Init static rx buffer num: 10
I (730) wifi: Init dynamic rx buffer num: 64
I (740) wifi: Init rx ampdu len mblock:7
I (740) wifi: Init lldesc rx ampdu entry mblock:4
I (740) wifi: wifi power manager task: 0x3ffc2a30 prio: 21 stack: 2560
I (750) wifi: wifi timer task: 3ffc3ab0, prio:22, stack:3584
I (810) wifi: mode : sta (30:ae:a4:04:80:84)
D (810) event: SYSTEM_EVENT_STA_START

I (840) app_sta: Connecting to AP...
I (960) wifi: n:1 0, o:1 0, ap:255 255, sta:1 0, prof:1
I (1940) wifi: state: init -> auth (b0)
I (1950) wifi: state: auth -> assoc (0)
I (1960) wifi: state: assoc -> run (10)
I (1990) wifi: connected with test, channel 1
D (1990) event: SYSTEM_EVENT_STA_CONNECTED, ssid:test, ssid_len:4, bssid:d0:5b:a8:c2:91:7e, channel:1, authmode:3
D (2324) event: SYSTEM_EVENT_STA_GOTIP, ip:192.168.1.120, mask:255.255.255.0, gw:192.168.1.1
I (2324) event: ip: 192.168.1.120, mask: 255.255.255.0, gw: 192.168.1.1
I (2324) app_sta: Connected.
I (9964) wifi: state: run -> auth (3a0)
I (9964) wifi: n:1 0, o:1 0, ap:255 255, sta:1 0, prof:1
D (9964) event: SYSTEM_EVENT_STA_DISCONNECTED, ssid:test, ssid_len:4, bssid:d0:5b:a8:c2:91:7e, reason:3
I (9994) app_sta: Wifi disconnected, try to connect ...
I (18164) wifi: n:11 0, o:1 0, ap:255 255, sta:11 0, prof:1
I (18174) wifi: state: auth -> auth (b0)
I (18174) wifi: state: auth -> assoc (0)
I (18184) wifi: state: assoc -> run (10)
I (18224) wifi: connected with test, channel 11
D (18234) event: SYSTEM_EVENT_STA_CONNECTED, ssid:test, ssid_len:4, bssid:d0:5b:a8:c2:91:7e, channel:11, authmode:3
D (18894) event: SYSTEM_EVENT_STA_GOTIP, ip:192.168.1.120, mask:255.255.255.0, gw:192.168.1.1
I (18894) event: ip: 192.168.1.120, mask: 255.255.255.0, gw: 192.168.1.1
I (18894) app_sta: Connected.
I (28184) wifi: pm start, type:0

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

注意,为了查看更多的状态,我在连接过程中将 WiFi 热点关闭了一次,然后再打开该热点。

仔细查看日志,可以看到下面这些与状态改变相关的日志:

  • event: SYSTEM_EVENT_STA_START
  • event: SYSTEM_EVENT_STA_CONNECTED
  • event: SYSTEM_EVENT_STA_GOTIP # 获取到 IP 后,重启 WiFi 热点
  • event: SYSTEM_EVENT_STA_DISCONNECTED
  • app_sta: Wifi disconnected, try to connect ...
  • event: SYSTEM_EVENT_STA_CONNECTED
  • event: SYSTEM_EVENT_STA_GOTIP # 再次连接,并获取到 IP地址

整个过程非常清晰吧!而且我们还可以看到断网自动重连的现象。

除了app_sta: Wifi disconnected, try to connect ...这句话是由我们的应用程序打印的外,其它 Log 都是由系统自己 打印的。

【状态转换过程】

在我们的应用程序中,当我们调用函数 esp_wifi_start() 后,wifi 状态机就会开始运转。第一个状态是 START,此时我们的 event_handler() 函数会被调用一次,并进入 case SYSTEM_EVENT_STA_START 分支:

    case SYSTEM_EVENT_STA_START:
        ESP_LOGI(TAG, "Connecting to AP...");
        esp_wifi_connect();
        break;
  • 1
  • 2
  • 3
  • 4

在该状态来临时,我们调用了函数 esp_wifi_connect(); 让 ESP32 去连接 WiFi 热点。调用该函数后,wifi 驱动会尝试通过 IEEE 802.11 与热点建立连接。如果建立连接成功,则进入 CONNECTED 状态,并通过某种协议从 AP 处获取一个 IP 地址,如果获取成功,则进入 GOTIP 状态;如果建立连接失败,则会进入 DISCONNECTED 状态。

当我们将 WiFi 热点关闭时,wifi 驱动会发现与热点的通信失败(IEEE 802.11 有心跳机制),然后进入 DISCONNECTED 状态,此时我们的 event_handler() 函数会被调用一次,并进入 case SYSTEM_EVENT_STA_DISCONNECTED 分支:

    case SYSTEM_EVENT_STA_DISCONNECTED:
        ESP_LOGI(TAG, "Wifi disconnected, try to connect ...");
        esp_wifi_connect();
        break;
  • 1
  • 2
  • 3
  • 4

我们在这里再次调用了函数 esp_wifi_connect(),让 wifi 驱动再次尝试与热点建立连接。如果建立建立成功,则会再次进入 CONNECTGOTIP 这两个状态;如果建立连接失败,会再次进入 DISCONNECT 状态,依次反复循环,直到连接成功为止。这就是所谓的断网重连!

【深入分析状态机源码】

通过上面的分析,我们已经基本清楚了整个状态机的转换过程,但是这个状态机是如何工作的呢?我们需要继续分析源码,拿 esp_event_loop_init() 这个函数开刀。

【函数原型】

这里需要看两个函数的函数原型,其中一个是 esp_event_loop_init(),另一个是需要传递给该函数的 回调函数(callback)

先看 esp_event_loop_init()

/**
  * @brief  Initialize event loop
  *         Create the event handler and task
  *
  * @param  system_event_cb_t cb : application specified event callback, it can be modified by call esp_event_set_cb
  * @param  void *ctx : reserved for user
  *
  * @return ESP_OK : succeed
  * @return others : fail
  */
esp_err_t esp_event_loop_init(system_event_cb_t cb, void *ctx);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

它的作用已经在注释中说的非常清楚了:初始化事件 loop,创建事件的 handler 和任务。

包含两个参数:

  • system_event_cb_t cb,即回调函数,是一个函数指针,当状态机中有某个状态改变时,会调用这个回调函数。
  • void *ctx,回调函数相关的上下文(context),即系统在调用回调函数时需要传递给回调函数的参数。

再看看 system_event_cb_t

/**
  * @brief  Application specified event callback function
  *
  * @param  void *ctx : reserved for user
  * @param  system_event_t *event : event type defined in this file
  *
  * @return ESP_OK : succeed
  * @return others : fail
  */
typedef esp_err_t (*system_event_cb_t)(void *ctx, system_event_t *event);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

它使用 typedef 定义了一个函数指针类型system_event_cb_t,它有参数:

  • void *ctx:回调函数相关的上下文(context),这个是由应用程序指定的,即传递给函数 esp_event_loop_init() 的第二个参数。
  • system_event_t *event:事件,可以理解为状态机的状态。回调函数被调用时,会根据这个参数来判断当前的状态机处于哪个状态。所以,在回调函数内部,是一个 switch…case… 结构。

【初始化过程】

下面开始具体分析源码。

esp_err_t esp_event_loop_init(system_event_cb_t cb, void *ctx)
{
    if (s_event_init_flag) {
        // 防止重复初始化
        return ESP_FAIL;
    }
    s_event_handler_cb = cb;
    s_event_ctx = ctx;
    // 创建一个 event 队列
    s_event_queue = xQueueCreate(CONFIG_SYSTEM_EVENT_QUEUE_SIZE, sizeof(system_event_t));
    // 创建 event loop 任务
    xTaskCreatePinnedToCore(esp_event_loop_task, "eventTask",
            ESP_TASKD_EVENT_STACK, NULL, ESP_TASKD_EVENT_PRIO, NULL, 0);

    s_event_init_flag = true;
    return ESP_OK;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

上面这段代码主要做了如下几件事儿:

  • 将传入给该函数的参数 cb 和 ctx 保存到全局变量 s_event_handler_cb 和 s_event_ctx
  • 创建一个事件队列 s_event_queue,这个队列用来存放事件。
  • 创建一个事件处理任务。

事件循环处理任务

在事件初始化时创建了事件循环处理任务,所以我们得继续查看该任务的代码。

static void esp_event_loop_task(void *pvParameters)
{
    while (1) {
        system_event_t evt;
        // 从初始化时所创建的事件队列中接收一个事件,接收的事件保存到变量 evt 中
        if (xQueueReceive(s_event_queue, &evt, portMAX_DELAY) == pdPASS) {
           // 如果接收事件成功,先调用默认的处理过程     
            esp_err_t ret = esp_event_process_default(&evt);
            if (ret != ESP_OK) {
                ESP_LOGE(TAG, "default event handler failed!");
            }
            // 再将该事件转给用户的应用程序
            ret = esp_event_post_to_user(&evt);
            if (ret != ESP_OK) {
                ESP_LOGE(TAG, "post event to user fail!");
            }
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

再看看默认处理过程:

esp_err_t esp_event_process_default(system_event_t *event)
{
    if (event == NULL) {
        // 先对入参进行判断。这里的入参即从事件队列中接收到的事件
        ESP_LOGE(TAG, "Error: event is null!");
        return ESP_FAIL;
    }

    // 这里是一些打印信息。打印出接收到的是啥事件。我们继续追踪该函数的源码
    // 的话,可以看到它里面只是对各事件调用 ESP_LOGD() 函数而已。 这里的日志
    // 级别是 Debug,所以我们在前面改变日志级别的时候,只将级别降低到了 Debug 
    // 级别,就能看到状态机中各状态的情况
    esp_system_event_debug(event);
    // 根据事件的id,先对事件的有效性进行检查
    if ((event->event_id < SYSTEM_EVENT_MAX)) {
        // 根据事件的 id,看看该事件有没有提供默认的处理函数
        if (default_event_handlers[event->event_id] != NULL) {
            // 如果有默认的处理函数,则调用该函数
            ESP_LOGV(TAG, "enter default callback");
            default_event_handlers[event->event_id](event);
            ESP_LOGV(TAG, "exit default callback");
        }
    } else {
        ESP_LOGE(TAG, "mismatch or invalid event, id=%d", event->event_id);
        return ESP_FAIL;
    }
    return ESP_OK;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

这里我们需要再看看 default_event_handlers 这个数组,这个数组的类型是函数指针:

static const system_event_handler_t default_event_handlers[SYSTEM_EVENT_MAX] = {
#ifdef CONFIG_WIFI_ENABLED
    [SYSTEM_EVENT_WIFI_READY]          = NULL,
    [SYSTEM_EVENT_SCAN_DONE]           = NULL,
    [SYSTEM_EVENT_STA_START]           = system_event_sta_start_handle_default,
    [SYSTEM_EVENT_STA_STOP]            = system_event_sta_stop_handle_default,
    [SYSTEM_EVENT_STA_CONNECTED]       = system_event_sta_connected_handle_default,
    [SYSTEM_EVENT_STA_DISCONNECTED]    = system_event_sta_disconnected_handle_default,
    [SYSTEM_EVENT_STA_AUTHMODE_CHANGE] = NULL,
    [SYSTEM_EVENT_STA_GOT_IP]          = system_event_sta_got_ip_default,
    [SYSTEM_EVENT_STA_WPS_ER_SUCCESS]  = NULL,
    [SYSTEM_EVENT_STA_WPS_ER_FAILED]   = NULL,
    [SYSTEM_EVENT_STA_WPS_ER_TIMEOUT]  = NULL,
    [SYSTEM_EVENT_STA_WPS_ER_PIN]      = NULL,
    [SYSTEM_EVENT_AP_START]            = system_event_ap_start_handle_default,
    [SYSTEM_EVENT_AP_STOP]             = system_event_ap_stop_handle_default,
    [SYSTEM_EVENT_AP_STACONNECTED]     = NULL,
    [SYSTEM_EVENT_AP_STADISCONNECTED]  = NULL,
    [SYSTEM_EVENT_AP_PROBEREQRECVED]   = NULL,
    [SYSTEM_EVENT_AP_STA_GOT_IP6]      = NULL,
#endif
#ifdef CONFIG_ETHERNET
    [SYSTEM_EVENT_ETH_START]           = system_event_eth_start_handle_default,
    [SYSTEM_EVENT_ETH_STOP]            = system_event_eth_stop_handle_default,
    [SYSTEM_EVENT_ETH_CONNECTED]       = system_event_eth_connected_handle_default,
    [SYSTEM_EVENT_ETH_DISCONNECTED]    = system_event_eth_disconnected_handle_default,
    [SYSTEM_EVENT_ETH_GOT_IP]          = NULL,
#endif
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

我们可以看出:

  • 当 wifi 状态机的状态变为 START 时,会调用函数 system_event_sta_start_handle_default()
  • 当 wifi 状态机的状态变为 CONNECTED 时,会调用函数 system_event_sta_connected_handle_default()
  • 当 wifi 状态机的状态变为 GOTIP 时,会调用函数 system_event_sta_got_ip_handle_default()
  • 当 wifi 状态机的状态变为 DISCONNECT 时,会调用函数 system_event_sta_disconnect_handle_default()
  • 当 wifi 状态机的状态变为 STOP 时,会调用函数 system_event_sta_stop_handle_default()

如果有兴趣,可以依次查看这些函数都干了些啥。这里由于篇幅太长,就不细看了。

然后我们再看看是如何将时间传递给应用程序的:

static esp_err_t esp_event_post_to_user(system_event_t *event)
{
    if (s_event_handler_cb) {
        // 如果 s_event_handler_cb 不为 NULL,则调用该函数
        return (*s_event_handler_cb)(s_event_ctx, event);
    }
    return ESP_OK;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

so easy!直接调用在 esp_event_loop_init() 时传入的回调函数,其中第一个参数表示在 esp_event_loop_init() 时传入的由应用程序指定的上下文参数,第二个参数表示当前的事件(即状态机的状态)。

终于搞明白了,哈哈O(∩∩)O哈哈~O(∩∩)O哈哈~。

等等,事件循环处理任务从事件队列中接收事件,这个事件是从哪儿来的呢?当然是 wifi 驱动库发送到这个事件中的,不过由于 wifi 驱动库没开源,所以我们没办法继续追踪源代码啦。

【总结】

其实这个状态机还是蛮简单的,由用户在应用程序传递一个回调函数给系统的事件处理模块,然后在该模块内部循环地接收并处理事件——调用默认的事件处理函数和用户设置的回调函数。


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

深入分析 ESP32 的 WiFi 状态机 的相关文章

随机推荐

  • 1055 The World's Richest (25 point(s))

    题解 很容易超时 各种优化 最后还出现段错误 心态爆炸的一道题 include
  • Intellij IDEA--Pycharm插件开发

    Intellij IDEA开发 Pycharm插件开发 开发android用Android Studio 开发java用intelluj IDEA 开发python用Pycharm 其实三者都是jetbrains公司的产品 三者任何一个开发
  • 1.mbedtls移植到STM32

    mbedtls学习笔记 1 关于mbedtls 2 STM32移植方法 2 1STM32cubemx移植 2 2手动移植1 2 3移植总结 2 4手动移植2 2 4 1移植方式2 2 4 2测试SHA1加密 1 关于mbedtls 1 主要
  • 链表的实现

    在学习链表之前我们要看看前面的顺序表的缺陷 1 中间 头部的插入删除 时间复杂度为O N 2 增容需要申请新空间 拷贝数据 释放旧空间 会有不小的消耗 3 增容一般是呈2倍的增长 势必会有一定的空间浪费 例如当前容量为100 满了以后增容到
  • python随机函数random要导入哪个包_python学习之随机函数random

    文章目录 python学习之随机函数random 提示 以下是本篇文章正文内容 下面案例可供参考 一 随机函数模块 要想使用随机数 需先导入随机数模块 导入模块 import random 也就是在开头写上import random 二 六
  • Spring源码分析之如何开始Spring源码分析

    一 概述 对于大多数第一次看spring源码的人来说 都会感觉不知从哪开始看起 因为spring项目源码由多个子项目组成 如spring beans spring context spring core spring aop spring
  • 程序员成熟的标志

    http www cnblogs com n216 archive 2011 05 16 2047327 html 程序员成长路线图 从入门到优秀 成熟篇 节选3 程序员在经历了若干年编程工作之后 很想知道自己水平到底如何 自己是否已经成为
  • spring boot社区养老医疗服务平台 毕业设计源码041148

    springboot社区养老医疗服务平台 摘 要 随着社会的发展 社会的各行各业都在利用信息化时代的优势 计算机的优势和普及使得各种信息系统的开发成为必需 社区养老医疗服务平台 主要的模块包括查看首页 站点内容 轮播图 公告栏 系统用户 管
  • 关于工业4.0和智能制造的总结以及背后的思考

    工业4 0是德国首先提出来的 美国叫 工业互联网 我国叫 中国制造2025 这三者其实本质都是一样的 核心都是智能制造 在即将过去的2015年里 互联网 这个概念已经是家户喻晓了 大众创业 万众创新 上升到国家战略 此时又恰逢工业4 0来袭
  • python 动态仪表盘_使用Echarts3实现渐变仪表盘需求

    Echarts 仪表盘实践 项目过程中遇到一个这样的需求 这明显是一个仪表盘类型的图表 打开echarts的官方文档 可以看到有一个默认的实现 如下 使用了默认的参数 option tooltip formatter a b c toolb
  • SylixOS热插拔子系统分析(二)

    1 系统与驱动 热插拔子系统中 系统与驱动层的交互分为热插拔事件和循环检测 热插拔事件是针对设备能够产生热插拔中断通知的情况 SylixOS可以将通知以异步的方式来处理 而循环检测是设备不能产生一个热插拔中断通知的情况 需要轮询检测某些事件
  • 数据在 Mocaverse 项目启动过程中是如何发挥作用的

    日期 2023年5月 数据源 Mocaverse Realm Ticket Collection Airdrop Mocaverse Optimizes an NFT Project at Launch Beyond NFT 是 Web3
  • 微软辅助服务器代码是什么东西,IT界“古玩”微软公开MS-DOS和Word源码

    对于软件巨头微软 从早期DOS到如今Windows 旗下应用软件工具可谓丰富 近日 从微软官方博客了解 宣布公开早期版本MS DOS和Word for Windows的源代码 在计算机历史博物馆的帮助下 这两个软件早期版本的源代码首度公开
  • 移动端日历插件(vue3-small-calendar)

    背景 同事做移动端项目时希望用到可平铺选择的日历插件 然度娘无过最后问题到我这边 虽然咱不是前端但是秉着技术问题不是问题且问题到我为止的原则 自己研究了2天完成了vue3 small calendar插件 此插件目前已发布npm 大家觉得可
  • 如何评价一个规划方案的合理性?记xx项目规划单位招标

    今天共4家公司设计单位参与投标 上午两家 下午两家 公司请了两位专家来评价各家方案 从整体规划方案水平上看 投标方案水平都不是特别突出 没有让人眼前一亮的方案 有的只是一些小创意小想法 对于新的规划理念 新的规划思维没有看到 另外大家对于西
  • 安装 inotify-tools

    摘要 inotify tools 是一款google出的用于监控文件系统的软件 一 软件下载地址官方站点地址 http inotify tools sourceforge net 仓库地址 https github com rvoicila
  • 华为OD机试真题(Java),整数编码(100%通过+复盘思路)

    一 题目描述 实现一个整数编码方法 使得待编码的数字越小 编码后所占用的字节数越小 编码规则如下 编码时7位一组 每个字节的低7位用于存储待编码数字的补码 字节的最高位表示后续是否还有字节 置1表示后面还有更多的字节 置0表示当前字节为最后
  • .sh脚本bash命令 从输出中过滤字符串(正则)替换json中value值 grep命令 sed命令 替换字符串

    bash命令 获取 sh文件当前目录 输出值赋给变量 grep命令查询文件 变量方法 sed命令正则替换文件或变量中字符串 带颜色输出 获取 sh文件当前目录 获取 sh文件当前目录 basepath cddirname 0 pwd bas
  • vue2实例

    目录 数据与方法 你提到vue有两种数据和方法 js是不是只有一种 vue2自带的实例和方法 vue2 会和jQuery冲突d的问题 vue2中被人吐槽的this vue3已改进 箭头函数和普通函数中的this 生命周期 数据与方法 没看懂
  • 深入分析 ESP32 的 WiFi 状态机

    本工程已托管到 GitHub 具体路径是 https github com tidyjiang8 esp32 projects tree master sta 在前一篇博客 让 ESP32 连接到你的 WiFi 热点 中 我们已经简单地分析