Wpa_supplicant工作流程分析之scan(从应用层到内核)(上)

news/2024/11/29 23:34:36/

Wpa_supplicant工作流程分析

wpa_supplicant官网:https://w1.fi/wpa_supplicant/

wpa_supplicant源码下载官网地址:https://w1.fi/releases/

本文分析基于版本:V2.6

1. 初始化

wpa_supplicant/main.c

在main()中,完成了四件事。先看一下源代码,我们再来一一分析。

int main(int argc, char *argv[])
{...//循环接收传入的参数for (;;) {c = getopt(argc, argv,"b:Bc:C:D:de:f:g:G:hi:I:KLMm:No:O:p:P:qsTtuvW");if (c < 0)break;switch (c) {case 'b':iface->bridge_ifname = optarg;break;case 'B':params.daemonize++;break;case 'c':iface->confname = optarg;break;case 'C':iface->ctrl_interface = optarg;break;case 'D':iface->driver = optarg;break;case 'd':
#ifdef CONFIG_NO_STDOUT_DEBUGprintf("Debugging disabled with ""CONFIG_NO_STDOUT_DEBUG=y build time ""option.\n");goto out;
#else /* CONFIG_NO_STDOUT_DEBUG */params.wpa_debug_level--;break;
#endif /* CONFIG_NO_STDOUT_DEBUG */case 'e':params.entropy_file = optarg;break;
#ifdef CONFIG_DEBUG_FILEcase 'f':params.wpa_debug_file_path = optarg;break;
#endif /* CONFIG_DEBUG_FILE */case 'g':params.ctrl_interface = optarg;break;case 'G':params.ctrl_interface_group = optarg;break;case 'h':usage();exitcode = 0;goto out;case 'i':iface->ifname = optarg;break;case 'I':iface->confanother = optarg;break;case 'K':params.wpa_debug_show_keys++;break;case 'L':license();exitcode = 0;goto out;
#ifdef CONFIG_P2Pcase 'm':params.conf_p2p_dev = optarg;break;
#endif /* CONFIG_P2P */case 'o':params.override_driver = optarg;break;case 'O':params.override_ctrl_interface = optarg;break;case 'p':iface->driver_param = optarg;break;case 'P':os_free(params.pid_file);params.pid_file = os_rel2abs_path(optarg);break;case 'q':params.wpa_debug_level++;break;
#ifdef CONFIG_DEBUG_SYSLOGcase 's':params.wpa_debug_syslog++;break;
#endif /* CONFIG_DEBUG_SYSLOG */
#ifdef CONFIG_DEBUG_LINUX_TRACINGcase 'T':params.wpa_debug_tracing++;break;
#endif /* CONFIG_DEBUG_LINUX_TRACING */case 't':params.wpa_debug_timestamp++;break;
#ifdef CONFIG_DBUScase 'u':params.dbus_ctrl_interface = 1;break;
#endif /* CONFIG_DBUS */case 'v':printf("%s\n", wpa_supplicant_version);exitcode = 0;goto out;case 'W':params.wait_for_monitor++;break;
#ifdef CONFIG_MATCH_IFACEcase 'M':params.match_iface_count++;iface = os_realloc_array(params.match_ifaces,params.match_iface_count,sizeof(struct wpa_interface));if (!iface)goto out;params.match_ifaces = iface;iface = &params.match_ifaces[params.match_iface_count -1];os_memset(iface, 0, sizeof(*iface));break;
#endif /* CONFIG_MATCH_IFACE */case 'N':iface_count++;iface = os_realloc_array(ifaces, iface_count,sizeof(struct wpa_interface));if (iface == NULL)goto out;ifaces = iface;iface = &ifaces[iface_count - 1];os_memset(iface, 0, sizeof(*iface));break;default:usage();exitcode = 0;goto out;}}exitcode = 0;//通过传入的参数,开始进行初始化工作global = wpa_supplicant_init(&params);if (global == NULL) {wpa_printf(MSG_ERROR, "Failed to initialize wpa_supplicant");exitcode = -1;goto out;} else {wpa_printf(MSG_INFO, "Successfully initialized ""wpa_supplicant");}......//调用wpa_supplicant_add_iface来添加控制接口,注意这里的global是wpa_supplicant_init()传回来的。wpa_s = wpa_supplicant_add_iface(global, &ifaces[i], NULL);if (wpa_s == NULL) {exitcode = -1;break;}}#ifdef CONFIG_MATCH_IFACEif (exitcode == 0)exitcode = wpa_supplicant_init_match(global);
#endif /* CONFIG_MATCH_IFACE *///如果控制窗口添加成功,则调用wpa_supplicant_run()让wpa_supplicant事件主循环run起来if (exitcode == 0)exitcode = wpa_supplicant_run(global);......
}
  1. 解析init.rc(这种情况是设备开机自启动wpa_supplicant进程时wpa_supplicant做的操作)或者是用户输入(如我们手动启动wpa_supplicant进程)的相关参数。一般格式如下:
wpa_supplicant/system/bin/wpa_supplicant -Dwext -iwlan0 -c /data/misc/wifi/wpa_supplicant.conf-D : 表示用那种驱动程序,一般默认是wext。现在大部分都是用nl80211。
-i : 表示网络接口名称
-c : 表示配置文件名称从源码中我们可以看到,在main中,wpa_supplicant会根据传进来的参数填充iface和param这两个结构体。
  1. 调用wpa_supplicant_init()进行一些初始化的操作。

    wpa_supplicant_init()返回的数据指针可用于添加和删除

    网络接口。

  2. 调用wpa_supplicant_add_iface()来添加控制接口。

    1. 创建一个wpa_supplicant结构体。
    2. 调用wpa_supplicant_init_iface()去做一些出初始化接口的工作。
  3. 调用wpa_supplicant_run()运行wpa_supplicant事件主循环。

    对eloop循环进行配置后调用eloop_run)()让eloop循环run起来。然后wpa_supplicant就在这个循环中处理来自上层的命令和来自下层的事件。

2. wpa_supplicant如何接收和响应上层命令

void eloop_run(void)
{
#ifdef CONFIG_ELOOP_POLLint num_poll_fds;int timeout_ms = 0;
#endif /* CONFIG_ELOOP_POLL */
#ifdef CONFIG_ELOOP_SELECTfd_set *rfds, *wfds, *efds;struct timeval _tv;
#endif /* CONFIG_ELOOP_SELECT */
#ifdef CONFIG_ELOOP_EPOLLint timeout_ms = -1;
#endif /* CONFIG_ELOOP_EPOLL */
#ifdef CONFIG_ELOOP_KQUEUEstruct timespec ts;
#endif /* CONFIG_ELOOP_KQUEUE */int res;struct os_reltime tv, now;#ifdef CONFIG_ELOOP_SELECTrfds = os_malloc(sizeof(*rfds));wfds = os_malloc(sizeof(*wfds));efds = os_malloc(sizeof(*efds));if (rfds == NULL || wfds == NULL || efds == NULL)goto out;
#endif /* CONFIG_ELOOP_SELECT */while (!eloop.terminate &&(!dl_list_empty(&eloop.timeout) || eloop.readers.count > 0 ||eloop.writers.count > 0 || eloop.exceptions.count > 0)) {struct eloop_timeout *timeout;if (eloop.pending_terminate) {/** This may happen in some corner cases where a signal* is received during a blocking operation. We need to* process the pending signals and exit if requested to* avoid hitting the SIGALRM limit if the blocking* operation took more than two seconds.*/eloop_process_pending_signals();if (eloop.terminate)break;}timeout = dl_list_first(&eloop.timeout, struct eloop_timeout,list);if (timeout) {os_get_reltime(&now);if (os_reltime_before(&now, &timeout->time))os_reltime_sub(&timeout->time, &now, &tv);elsetv.sec = tv.usec = 0;
#if defined(CONFIG_ELOOP_POLL) || defined(CONFIG_ELOOP_EPOLL)timeout_ms = tv.sec * 1000 + tv.usec / 1000;
#endif /* defined(CONFIG_ELOOP_POLL) || defined(CONFIG_ELOOP_EPOLL) */
#ifdef CONFIG_ELOOP_SELECT_tv.tv_sec = tv.sec;_tv.tv_usec = tv.usec;
#endif /* CONFIG_ELOOP_SELECT */
#ifdef CONFIG_ELOOP_KQUEUEts.tv_sec = tv.sec;ts.tv_nsec = tv.usec * 1000L;
#endif /* CONFIG_ELOOP_KQUEUE */}#ifdef CONFIG_ELOOP_POLLnum_poll_fds = eloop_sock_table_set_fds(&eloop.readers, &eloop.writers, &eloop.exceptions,eloop.pollfds, eloop.pollfds_map,eloop.max_pollfd_map);res = poll(eloop.pollfds, num_poll_fds,timeout ? timeout_ms : -1);
#endif /* CONFIG_ELOOP_POLL */
#ifdef CONFIG_ELOOP_SELECTeloop_sock_table_set_fds(&eloop.readers, rfds);eloop_sock_table_set_fds(&eloop.writers, wfds);eloop_sock_table_set_fds(&eloop.exceptions, efds);res = select(eloop.max_sock + 1, rfds, wfds, efds,timeout ? &_tv : NULL);
#endif /* CONFIG_ELOOP_SELECT */
#ifdef CONFIG_ELOOP_EPOLLif (eloop.count == 0) {res = 0;} else {res = epoll_wait(eloop.epollfd, eloop.epoll_events,eloop.count, timeout_ms);}
#endif /* CONFIG_ELOOP_EPOLL */
#ifdef CONFIG_ELOOP_KQUEUEif (eloop.count == 0) {res = 0;} else {res = kevent(eloop.kqueuefd, NULL, 0,eloop.kqueue_events, eloop.kqueue_nevents,timeout ? &ts : NULL);}
#endif /* CONFIG_ELOOP_KQUEUE */if (res < 0 && errno != EINTR && errno != 0) {wpa_printf(MSG_ERROR, "eloop: %s: %s",
#ifdef CONFIG_ELOOP_POLL"poll"
#endif /* CONFIG_ELOOP_POLL */
#ifdef CONFIG_ELOOP_SELECT"select"
#endif /* CONFIG_ELOOP_SELECT */
#ifdef CONFIG_ELOOP_EPOLL"epoll"
#endif /* CONFIG_ELOOP_EPOLL */
#ifdef CONFIG_ELOOP_KQUEUE"kqueue"
#endif /* CONFIG_ELOOP_EKQUEUE */, strerror(errno));goto out;}eloop.readers.changed = 0;eloop.writers.changed = 0;eloop.exceptions.changed = 0;eloop_process_pending_signals();/* check if some registered timeouts have occurred */timeout = dl_list_first(&eloop.timeout, struct eloop_timeout,list);if (timeout) {os_get_reltime(&now);if (!os_reltime_before(&now, &timeout->time)) {void *eloop_data = timeout->eloop_data;void *user_data = timeout->user_data;eloop_timeout_handler handler =timeout->handler;eloop_remove_timeout(timeout);handler(eloop_data, user_data);}}if (res <= 0)continue;if (eloop.readers.changed ||eloop.writers.changed ||eloop.exceptions.changed) {/** Sockets may have been closed and reopened with the* same FD in the signal or timeout handlers, so we* must skip the previous results and check again* whether any of the currently registered sockets have* events.*/continue;}#ifdef CONFIG_ELOOP_POLLeloop_sock_table_dispatch(&eloop.readers, &eloop.writers,&eloop.exceptions, eloop.pollfds_map,eloop.max_pollfd_map);
#endif /* CONFIG_ELOOP_POLL */
/*
*调用select系统调用以轮询几类事件:相关fd是否可读、可写
*是否有异常发生、超时。
*如果相关事件发生时,eloop_sock_table_dispatch()将按照*下面的方式来处理它,它会通过调用相关的handler()回调函数来*处理这些事件。
*/
#ifdef CONFIG_ELOOP_SELECTeloop_sock_table_dispatch(&eloop.readers, rfds);eloop_sock_table_dispatch(&eloop.writers, wfds);eloop_sock_table_dispatch(&eloop.exceptions, efds);
#endif /* CONFIG_ELOOP_SELECT */
#ifdef CONFIG_ELOOP_EPOLLeloop_sock_table_dispatch(eloop.epoll_events, res);
#endif /* CONFIG_ELOOP_EPOLL */
#ifdef CONFIG_ELOOP_KQUEUEeloop_sock_table_dispatch(eloop.kqueue_events, res);
#endif /* CONFIG_ELOOP_KQUEUE */}eloop.terminate = 0;
out:
#ifdef CONFIG_ELOOP_SELECTos_free(rfds);os_free(wfds);os_free(efds);
#endif /* CONFIG_ELOOP_SELECT */return;
}

3. handler()回调函数的注册

以read事件为例:

不同类型的事件来源会写自己的handler函数,如udp、unix通用等。最终都会调用一下接口:
-->eloop_register_read_sock()-->eloop_register_sock()-->eloop_get_sock_table() //不同类型返回不同的table-->eloop_sock_table_add_sock() //将其添加到table->table[i].handler

通过上面这个流程,先关handler()回调函数就被注册到table,当相关事件发生后,在调用相关的handler()对事件进行处理。

4. wpa_supplicant 扫描流程分析

在上一小节中说到了注册handler()回调函数,其中一个read事件的handler()注册接口如下:

eloop_register_read_sock(priv->sock, wpa_supplicant_ctrl_iface_receive,wpa_s, priv);

该接口注册了一个wpa_supplicant_ctrl_iface_receive()的handler。

  1. wpa_supplicant_ctrl_iface_receive()的主要工作是监听socket,并且处理来自相关socket的command。下面来看下该函数的源码:
static void wpa_supplicant_ctrl_iface_receive(int sock, void *eloop_ctx,void *sock_ctx)
{struct wpa_supplicant *wpa_s = eloop_ctx;struct ctrl_iface_priv *priv = sock_ctx;char buf[4096];int res;struct sockaddr_storage from;socklen_t fromlen = sizeof(from);char *reply = NULL, *reply_buf = NULL;size_t reply_len = 0;int new_attached = 0;//recvform()监听相关socketres = recvfrom(sock, buf, sizeof(buf) - 1, 0,(struct sockaddr *)&from,&fromlen);if (res < 0) {wpa_printf(MSG_ERROR,"recvfrom(ctrl_iface): 				%s",strerror(errno));return;}buf[res] = '\0';if (os_strcmp(buf, "ATTACH") == 0) {if (wpa_supplicant_ctrl_iface_attach(&priv->ctrl_dst, &from,fromlen, 0))reply_len = 1;else {new_attached = 1;reply_len = 2;}} else if (os_strcmp(buf, "DETACH") == 0) {if(wpa_supplicant_ctrl_iface_detach(&priv->ctrl_dst, &from,fromlen))reply_len = 1;elsereply_len = 2;} else if (os_strncmp(buf, "LEVEL ", 6) == 0) 		{if (wpa_supplicant_ctrl_iface_level(priv, &from, fromlen,buf + 6))reply_len = 1;elsereply_len = 2;} else {
/*
*将相关命令,交给wpa_supplicant_ctrl_iface_process()  *来处理。
*/reply_buf = wpa_supplicant_ctrl_iface_process(wpa_s, buf,&reply_len);reply = reply_buf;
....
/*
*将相关的处理结果反馈给相应的socket。
*/if (reply) {wpas_ctrl_sock_debug("ctrl_sock-sendto", sock, reply,reply_len);if (sendto(sock, reply, reply_len, 0, (struct sockaddr *) &from,fromlen) < 0) {int _errno = errno;wpa_dbg(wpa_s, MSG_DEBUG,"ctrl_iface sendto failed: %d - %s",_errno, strerror(_errno));if (_errno == ENOBUFS || _errno == EAGAIN) {/** The socket send buffer could be full. This* may happen if client programs are not* receiving their pending messages. Close and* reopen the socket as a workaround to avoid* getting stuck being unable to send any new* responses.*/sock = wpas_ctrl_iface_reinit(wpa_s, priv);if (sock < 0) {wpa_dbg(wpa_s, MSG_DEBUG, "Failed to reinitialize ctrl_iface socket");}}if (new_attached) {wpa_dbg(wpa_s, MSG_DEBUG, "Failed to send response to ATTACH - detaching");new_attached = 0;wpa_supplicant_ctrl_iface_detach(&priv->ctrl_dst, &from, fromlen);}}}os_free(reply_buf);if (new_attached)eapol_sm_notify_ctrl_attached(wpa_s->eapol);
}
  1. wpa_supplicant_ctrl_iface_process()

    wpa_supplicant_ctrl_iface_process()接口会对命令的类型进行判断,如“SCAN”

···else if(os_strcmp(buf,"SCAN") == 0)if(!wpa_s->scan_ongoing){wpa_s->scan_req = 2;wpa_supplicant_req_scan(wpa_s,0,0);}elsewpa_printf(MSG_DEBUG,"Ongoing Scan 							action...");
...
  1. wpa_ctrl_scan() 处理扫描命令
  2. wpa_supplicant_req_scan 安排对附近的AP进行扫描
  3. wpa_supplicant通过前面讲到的‘-D“参数调用driver的ioctl接口。这里可以看到wpa_supplicant对下的一些驱动接口。
const struct wpa_driver_ops *const wpa_drivers[] =
{
#ifdef CONFIG_DRIVER_NL80211&wpa_driver_nl80211_ops,
#endif /* CONFIG_DRIVER_NL80211 */
#ifdef CONFIG_DRIVER_WEXT&wpa_driver_wext_ops,
#endif /* CONFIG_DRIVER_WEXT */
#ifdef CONFIG_DRIVER_HOSTAP&wpa_driver_hostap_ops,
#endif /* CONFIG_DRIVER_HOSTAP */
#ifdef CONFIG_DRIVER_BSD&wpa_driver_bsd_ops,
#endif /* CONFIG_DRIVER_BSD */
#ifdef CONFIG_DRIVER_OPENBSD&wpa_driver_openbsd_ops,
#endif /* CONFIG_DRIVER_OPENBSD */
#ifdef CONFIG_DRIVER_NDIS&wpa_driver_ndis_ops,
#endif /* CONFIG_DRIVER_NDIS */
#ifdef CONFIG_DRIVER_WIRED&wpa_driver_wired_ops,
#endif /* CONFIG_DRIVER_WIRED */
#ifdef CONFIG_DRIVER_MACSEC_QCA&wpa_driver_macsec_qca_ops,
#endif /* CONFIG_DRIVER_MACSEC_QCA */
#ifdef CONFIG_DRIVER_ROBOSWITCH&wpa_driver_roboswitch_ops,
#endif /* CONFIG_DRIVER_ROBOSWITCH */
#ifdef CONFIG_DRIVER_ATHEROS&wpa_driver_atheros_ops,
#endif /* CONFIG_DRIVER_ATHEROS */
#ifdef CONFIG_DRIVER_NONE&wpa_driver_none_ops,
#endif /* CONFIG_DRIVER_NONE */NULL
};//以wext为例看看它支持的相关接口
const struct wpa_driver_ops wpa_driver_wext_ops = {.name = "wext",.desc = "Linux wireless extensions (generic)",.get_bssid = wpa_driver_wext_get_bssid,.get_ssid = wpa_driver_wext_get_ssid,.set_key = wpa_driver_wext_set_key,.set_countermeasures = wpa_driver_wext_set_countermeasures,.scan2 = wpa_driver_wext_scan,.get_scan_results2 = wpa_driver_wext_get_scan_results,.deauthenticate = wpa_driver_wext_deauthenticate,.associate = wpa_driver_wext_associate,.init = wpa_driver_wext_init,.deinit = wpa_driver_wext_deinit,.add_pmkid = wpa_driver_wext_add_pmkid,.remove_pmkid = wpa_driver_wext_remove_pmkid,.flush_pmkid = wpa_driver_wext_flush_pmkid,.get_capa = wpa_driver_wext_get_capa,.set_operstate = wpa_driver_wext_set_operstate,.get_radio_name = wext_get_radio_name,.signal_poll = wpa_driver_wext_signal_poll,.status = wpa_driver_wext_status,
};
  1. wpa_supplicant_scan —> wpa_supplicant_trigger_scan 请求driver进行scan
int wpa_supplicant_trigger_scan(struct wpa_supplicant *wpa_s,struct wpa_driver_scan_params *params)
{struct wpa_driver_scan_params *ctx;if (wpa_s->scan_work) {wpa_dbg(wpa_s, MSG_INFO, "Reject scan trigger since one is already pending");return -1;}ctx = wpa_scan_clone_params(params);if (!ctx ||radio_add_work(wpa_s, 0, "scan", 0, wpas_trigger_scan_cb, ctx) < 0){wpa_scan_free_params(ctx);wpa_msg(wpa_s, MSG_INFO, WPA_EVENT_SCAN_FAILED "ret=-1");return -1;}return 0;
}
  1. radio_add_work–>wpas_trigger_scan_cb(回调函数)–>wpa_drv_scan

    调用之前指定驱动所注册的函数,比如之前-D参数指定的驱动,假如指定的是wext,那么会调用到wpa_s->driver->scan2调用到wpa_driver_wext_scan(这个在前面有提到过,这个结构体wpa_driver_wext_ops会注册这个接口)通过ioctl将扫描指令下发给driver。

static inline int wpa_drv_scan(struct wpa_supplicant *wpa_s,struct wpa_driver_scan_params *params)
{
#ifdef CONFIG_TESTING_OPTIONSif (wpa_s->test_failure == WPAS_TEST_FAILURE_SCAN_TRIGGER)return -EBUSY;
#endif /* CONFIG_TESTING_OPTIONS */
//此处回去选择调用相关驱动注册的scan接口if (wpa_s->driver->scan2)return wpa_s->driver->scan2(wpa_s->drv_priv, params);return -1;
}
...
int wpa_driver_wext_scan(void *priv, struct wpa_driver_scan_params *params)
{....//省略无关代码
//通过ioctl()接口将扫描请求下发到driver,drv->ioctl_sock在下面会讲到if (ioctl(drv->ioctl_sock, SIOCSIWSCAN, &iwr) < 0) {wpa_printf(MSG_ERROR, "ioctl[SIOCSIWSCAN]: %s",strerror(errno));ret = -1;}....//省略无关代码
}  //ioctl()接口中使用了drv->ioctl_sock,它是来自于wpa_driver_wext_init(),该函数主要对wext driver接口进行初始化,如下:
void * wpa_driver_wext_init(void *ctx, const char *ifname)
{struct wpa_driver_wext_data *drv;struct netlink_config *cfg;struct rfkill_config *rcfg;char path[128];struct stat buf;drv = os_zalloc(sizeof(*drv));if (drv == NULL)return NULL;drv->ctx = ctx;os_strlcpy(drv->ifname, ifname, sizeof(drv->ifname));os_snprintf(path, sizeof(path), "/sys/class/net/%s/phy80211", ifname);if (stat(path, &buf) == 0) {wpa_printf(MSG_DEBUG, "WEXT: cfg80211-based driver detected");drv->cfg80211 = 1;wext_get_phy_name(drv);}
//我们用到的drv->ioctl_sock,在此处创建以及初始化drv->ioctl_sock = socket(PF_INET, SOCK_DGRAM, 0);if (drv->ioctl_sock < 0) {wpa_printf(MSG_ERROR, "socket(PF_INET,SOCK_DGRAM): %s",strerror(errno));goto err1;}cfg = os_zalloc(sizeof(*cfg));if (cfg == NULL)goto err1;cfg->ctx = drv;cfg->newlink_cb = wpa_driver_wext_event_rtm_newlink;cfg->dellink_cb = wpa_driver_wext_event_rtm_dellink;drv->netlink = netlink_init(cfg);if (drv->netlink == NULL) {os_free(cfg);goto err2;}rcfg = os_zalloc(sizeof(*rcfg));if (rcfg == NULL)goto err3;rcfg->ctx = drv;os_strlcpy(rcfg->ifname, ifname, sizeof(rcfg->ifname));rcfg->blocked_cb = wpa_driver_wext_rfkill_blocked;rcfg->unblocked_cb = wpa_driver_wext_rfkill_unblocked;drv->rfkill = rfkill_init(rcfg);if (drv->rfkill == NULL) {wpa_printf(MSG_DEBUG, "WEXT: RFKILL status not available");os_free(rcfg);}drv->mlme_sock = -1;if (wpa_driver_wext_finish_drv_init(drv) < 0)goto err3;wpa_driver_wext_set_auth_param(drv, IW_AUTH_WPA_ENABLED, 1);return drv;err3:rfkill_deinit(drv->rfkill);netlink_deinit(drv->netlink);
err2:close(drv->ioctl_sock);
err1:os_free(drv);return NULL;
}

至此,wpa_supplicant的工作已经全部完成。接下,这个scan指令会继续下发kernel中交给driver去实现。


http://www.ppmy.cn/news/521430.html

相关文章

自制Linux功能板

Cloud 一、前言二、电源管理2.1 锂电池2.2 充放电路2.3 稳压电路&#xff08;5V->3.3V&#xff09; 三 、启动方式boot3.1 概述3.2 电路图 四、USB_HUB电路4.1 概述4.2 硬件电路 五、mpu6050设计及应用5.1 概述5.2 硬件电路5.3 软件编写 六、ipslcd设计及应用6.1 概述6.2 硬…

在Talon AD7200路由器移植LEDE环境

这个项目是本人对Talon Tools: Practical IEEE 802.11ad Research | talon-tools (seemoo-lab.github.io)上的一个项目&#xff1a;Talon Tools: Practical IEEE 802.11ad Research的实现&#xff0c;同时由于该项目中存在不少误区和省略掉的点&#xff0c;本人对其进行了进一步…

wiFI基础知识----wpa_supplicant

1 wpa_supplicant 源码下载地址&#xff1a; wpa_supplicant-2.10 下载地址&#xff1a;wpa_supplicant-2.10 2 wpa_supplicant 学习总结 wpa_supplicant 学习参考网址&#xff1a;wpa_supplicant 2.1 wpa_supplicant 基本概念&#xff1a; ** wpa_supplicant 是什么&…

Matlab实现电网仿真(附上完整仿真源码)

电网仿真是电力系统研究和设计中非常重要的一部分。Matlab作为一种高效的数值计算工具&#xff0c;已经成为电网仿真中不可或缺的工具之一。本文将介绍如何使用Matlab实现电网仿真。 文章目录 1. 电网模型建立2. 电网仿真参数设置3. 电网仿真结果分析4. 完整仿真源码下载 1. 电…

iCON艾肯五代声卡Dyna版本驱动(全系列)

iCON艾肯声卡第五代声卡驱动直发方官方还没有发布&#xff0c;这需要找经销商要才有。支持声卡型号&#xff1a;Icon Cube2Nano Dyna&#xff0c;Icon_Duo22 Dyna&#xff0c;Icon MicU Dyna&#xff0c;Icon MobileR Dyna&#xff0c;Icon MobileUmini Dyna&#xff0c;Icon U…

艾韵智能A1刷机的一些问题

随着艾韵智能服务器的关闭&#xff0c;A1也失去了用武之地&#xff0c;不过随着第三方固件的诞生&#xff0c;它又复活了&#xff0c;不过刷机过程中有点波折&#xff0c;现给出一些参考的解决方法。 首先刷机方法都是按照潘老师的方法&#xff0c;接入飞阳物联&#xff0c;不…

【小米米家对接连载】 安信可 ESP8266-12S模块作为米家通用模块,直连小米米家平台,小爱同学语音控制;

文章目录 一. 准备工作二. 通过STM32代码设置产品model三. STM32单片机不断发送get_down指令一直查询模块返回的状态四. 模块核心指令的解答五. 关于模块返回的参数说明 最近在做一个需要通过米家APP进行智能插座的项目&#xff0c;为了方便直观的看到插座的开和关的状态&#…

NFC手机(小米2A刷机到Android4.4)HCE环境搭建

如果测试NFC手机的HCE功能&#xff0c;由于google的Android的4.4版本才支持HCE&#xff0c;需要NFC手机满足该条件&#xff0c;刷机成为选择。 一.环境搭建 测试NFC手机的HCE功能更需要搭建环境&#xff0c;由于HCE支持的手机操作系统为Android4.4,但是小米2A的Android操作系…