暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

鸿蒙Hi3516如何通过编程连接Wifi?

鸿蒙技术社区 2021-03-30
472

上一篇《Hi3516如何连接Wifi(一)》聊了一下怎样在 Hi3516 中用 wpa_supplicant 连接到 Wifi 热点,本文讲一下如何通过编程实现。



01

总体思路


首先我们需要搞清楚 Hi3516 中 Wifi 的相关模块,以及他们之间的关系,其实和 Linux 是很相似的。


首先,我们需要运行一个的 Daemon,也就是上文提到的 wpa_supplicant,负责对网卡的硬件调用,比如连接 wifi、断开 wifi、启动热点等等。


这个 Daemon 开放一个 socket 端口,外部程序可以通过本地连接向其发送指令实现间接对 wifi 的调用,这无疑是给我们提供了很大的便利,不用从底层重新造轮子了。


鸿蒙 OS 代码中,有一个示例,在 applications/sample/camera/communication/wpa_cli,实现了连接 Daemon、扫描热点、连接热点等功能。


现在方案就很明确了,第一启动 Daemon,第二向 Daemon 发送命令。下面我们就来详细分析如何实现。


02

启动 Daemon


查看代码:
//applications/sample/camera/communication/wpa_supplicant/src/wpa_sample.c


找到 main 函数,发现它只做了一件事情,那就是调用 pthread_create 创建了一个线程,线程执行的函数是 ThreadMain。


而 ThreadMain 也只做了一件事情,那就是加载 usr/lib/libwpa.so,然后执行了其中的 wpa_main 函数,同时把命令行参数传递了进去。


而 wpa_main 函数具体调用网卡就是通过 hdf 框架向内核态发送消息了,这里就不再赘述。
static voidThreadMain()
{
    printf("[WpaSample]init wpa_supplicant.\n");

    void *handleLibWpa = dlopen("/usr/lib/libwpa.so", RTLD_NOW | RTLD_LOCAL);
    if (handleLibWpa == NULL) {
        printf("[WpaSample]dlopen libwpa failed.\n");
        return NULL;
    }
    int (*func)(intchar **) = NULL;
    func =  dlsym(handleLibWpa, "wpa_main");
    if (func == NULL) {
        dlclose(handleLibWpa);
        printf("[WpaSample]dlsym wpa_main failed.\n");
        return NULL;
    }
    int ret = func(g_wpaArgc, g_wpaArg);

    printf("[WpaSample]run wpa_main failed, ret:%d.\n", ret);
    for (int i = 0; i < g_wpaArgc; i++) {
        printf("[WpaSample]arg %d:%s.\n", i, g_wpaArg[i]);
    }

    if (dlclose(handleLibWpa) != 0) {
        printf("[WpaSample]dlclose libwpa failed.\n");
        return NULL;
    }
    return NULL;
}

int main(int argc, char *argv[])
{
    g_wpaArgc = argc;
    for (int i = 0; i < g_wpaArgc; i++) {
        g_wpaArg[i] = argv[i];
    }

    int ret = pthread_create(&g_wpaThread, NULL, ThreadMain, NULL);
    if (ret != 0) {
        printf("[WpaSample]create thread failed error:%s.\n", strerror(ret));
        return 1;
    }
    pthread_join(g_wpaThread, NULL);
    return 0;
}


我们要做的就是仿照 main 函数写自己的代码,把参数固定就可以了。


我们的参数是这样的:
g_wpaArg[0]="",g_wpaArg[1]="-iwlan0",g_wpaArg[2]="-c/etc/wpa_supplicant.conf"


其中第 0 个参数是可执行文件的名称,这里可以随意填或者直接留空。


如果只是想启动 Daemon,不连接到任何 Wifi 热点,那这里第二个参数 -c 指向的 .conf 文件中,不应该包含 ssid 和 psk,也就是直接使用系统自带的默认 conf 就可以。


我们在上一篇文章中修改了 wpa_supplicant.conf,加入了 ssid 和 psk。


这里做一个改进,原 wpa_supplicant.conf 保持不变,新增一个 wpa_supplicant_(你的热点名称).conf,加入 ssid 和 psk。


然后修改:
//applications/sample/camera/communication/wpa_supplicant/BUILD.gn


添加需要 copy 的文件:
copy("config2") {
    sources = [
        "config/wpa_supplicant_(xxx).conf"
    ]
    outputs = [
        "$root_out_dir/etc/wpa_supplicant_(xxx).conf"
    ]
}


这样我们 wpa_supplicant 就有两种操作了,只启动 Daemon,和启动 Daemon 且连接到指定热点,只需要改变 -c 指定的 conf 文件。


补充一下,我曾尝试过使用 system 函数执行 wpa_supplicant 的方式启动 Daemon,但是失败了,原因是鸿蒙暂时还不支持 system 函数。


具体可以看一下 system 的代码实现 \\third_party\musl\src\process\system.c:
int system(const char *cmd)
{
    pid_t pid;
    sigset_t old, reset;
    struct sigaction sa = { .sa_handler = SIG_IGN }, oldint, oldquit;
    int status = -1, ret;
    posix_spawnattr_t attr;

    unsupported_api(__FUNCTION__);//不受支持的api
...


另外,尝试了用 fork 创建线程也是可行的。


还有一个文件权限问题。如果你用上述方法编写一个控制台程序来运行是没有问题的,无非就是重写了一个 sample 里的 wpa_supplicant。


但是当你在 hap 中通过 ace 调用时就出现了错误:
OHOS # 01-01 00:40:03.661 17 59 I 03900/ACE: InitWifi invoked!
[WpaSample]init wpa_supplicant.
01-01 00:40:03.661 17 59 I 03900/ACE: InitDaemon2
Successfully initialized wpa_supplicant
[HDF:E/hdf_syscall_adapter]Open file node failed: /dev/hdfwifi
[HDF:E/HDF_LOG_TAG]WpaMsgServiceInit: fail to get remote service!


看样子是打开 dev/hdfwifi 失败了,这个问题我研究了很久,最后意识到 hap 的执行用户可能和 shell 不同。


shell 是 root 用户在执行,而 hap 肯定不是 root 在执行,这导致了权限不足。


我看了一下 dev/hdfwifi 的权限:
OHOS # ls /dev
Directory /dev:
(略)
-rw-rw-r-- 0        u:0     g:99    hdfwifi
(略)


其他用户是 r 权限,显然我们也需要 w 权限。执行 chmod 0666 dev/hdfwifi 就可以了,但烧写后通过连接 shell 做这件事很不方便,最好能自动化。


这里我们可以借助鸿蒙系统初始化阶段执行的 job 来实现我们的目的,在 \\base\startup\services\init_lite\src\main.c 负责执行系统启动后的任务,包括各种 job 和 service,job 分为 pre-init,init,post-init 三个阶段。


具体要执行哪些命令,都写在 \\vendor\huawei\camera\init_configs\init_liteos_a_3516dv300.cfg 配置文件中。


我们要做的就是在 job 中找到 post-init,然后在 cmds 添加我们的指令 chmod 0666 dev/hdfwifi:
{
    "jobs" : [{
            "name" : "pre-init",
            "cmds" : [
                "mkdir /storage/data/log",
                (略)
            ]
        }, {
            "name" : "init",
            "cmds" : [
                "start shell",
                (略)
            ]
        }, {
            "name" : "post-init",
            "cmds" : [
                "chown 0 99 /dev/dev_mgr",
                "chown 0 99 /dev/hdfwifi",
                "chmod 0666 /dev/hdfwifi",//这里


下一篇再将如何连接 Daemon,真正实现连接 Wifi,以及如何通过 ACE 在 UI 界面中操作连接 Wifi。


👇点击关注鸿蒙技术社区👇

专注开源技术,共建鸿蒙生态


“阅读原文”了解更多

文章转载自鸿蒙技术社区,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论