WIFI模块开发教程之W600网络篇6:Airkiss配网

前言

本文研究如何使用微信Airkiss协议对模块进行配网,所谓配网即是说通知模块需要连接的路由器ssid和password的一种机制。

一、理论基础

1.Airkiss原理

设备处于混杂模式监听无线包,APP每隔一小段时间发送广播包/组播包,通过路由器转发,当设备和路由器处于同一信道的时候,设备能够收到有效的数据,而无线包中可见字段只有length,因此Airkiss和市面上所谓的一键配网实际上都是通过对length进行编码传输路由器ssid和password给设备的一种方式。

2.Airkiss配网流程

在这里插入图片描述

主要步骤如下所示:

设备处于混杂模式监听无线包,并每隔100ms切换一次信道。

设备信道锁定后,不再切换

APP下发通过length编码的ssid和password

设备按照相同的规则解析包内容,获取路由器ssid和password

二、使用实例

1.程序分析

1.1 设备信道切换

static void airkiss_switch_channel(void *parameter)
{
    g_current_channel++;
    if (g_current_channel > MAX_CHANNEL_NUM)
    {
        g_current_channel = 1;
    }
    rt_wlan_set_channel(g_wlan_device, g_current_channel);
    airkiss_change_channel(ak_contex);
    AIRKISS_PRINTF("Switch channel %d \n", g_current_channel);
}

1.2 混杂包监听回调函数

static void airkiss_monitor_callback(uint8_t *data, int len, void *user_data)
{
    airkiss_recv_ret = airkiss_recv(ak_contex, data, len);
    if (airkiss_recv_ret == AIRKISS_STATUS_CHANNEL_LOCKED)
    {
        rt_timer_stop(g_switch_timer);
        AIRKISS_PRINTF("Lock channel in %d \n", g_current_channel);
        rt_timer_start(g_doing_timer);
    }
    else if (airkiss_recv_ret == AIRKISS_STATUS_COMPLETE)
    {
        rt_timer_stop(g_doing_timer);
        rt_sem_release(g_cfg_done_sem);
        AIRKISS_PRINTF("AIRKISS_STATUS_COMPLETE \n");
    }
}

1.3 发送配网后收到的random,通知APP配网成功

static void airkiss_send_notification_thread(void *parameter)
{
    int sock = -1;
    int udpbufsize = 2;
    uint8_t random = (uint32_t)parameter;
    struct sockaddr_in g_stUDPBCAddr, g_stUDPBCServerAddr;

    sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0)
    {
        AIRKISS_PRINTF("notify create socket error!\n");
        goto _exit;
    }

    g_stUDPBCAddr.sin_family = AF_INET;
    g_stUDPBCAddr.sin_port = htons(10000);
    g_stUDPBCAddr.sin_addr.s_addr = htonl(0xffffffff);
    rt_memset(&(g_stUDPBCAddr.sin_zero), 0, sizeof(g_stUDPBCAddr.sin_zero));

    g_stUDPBCServerAddr.sin_family = AF_INET;
    g_stUDPBCServerAddr.sin_port = htons(10000);
    g_stUDPBCServerAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    rt_memset(&(g_stUDPBCServerAddr.sin_zero), 0, sizeof(g_stUDPBCServerAddr.sin_zero));

    if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &udpbufsize, sizeof(int)) != 0)
    {
        AIRKISS_PRINTF("notify socket setsockopt error\n");
        goto _exit;
    }

    if (bind(sock, (struct sockaddr *)&g_stUDPBCServerAddr, sizeof(g_stUDPBCServerAddr)) != 0)
    {
        AIRKISS_PRINTF("notify socket bind error\n");
        goto _exit;
    }

    for (int i = 0; i <= 20; i++)
    {
        int ret = sendto(sock, (char *)&random, 1, 0, (struct sockaddr *)&g_stUDPBCAddr, sizeof(g_stUDPBCAddr));
        rt_thread_delay(10);
    }

    AIRKISS_PRINTF("airkiss notification thread exit!\n");

_exit:
    if (sock >= 0)
    {
        close(sock);
    }
}

1.4 配网入口函数

static void airkiss_thread_entry(void *parameter)
{
    int result;

    g_switch_timer = rt_timer_create("switch_channel",
                                     airkiss_switch_channel,
                                     RT_NULL,
                                     AIRKISS_SWITCH_TIMER,
                                     RT_TIMER_FLAG_SOFT_TIMER | RT_TIMER_FLAG_PERIODIC);
    if (!g_switch_timer)
    {
        rt_kprintf("Create airkiss swtich channel timer failed \n");
        goto _exit;
    }

    g_doing_timer = rt_timer_create("doing_timeout",
                                    airkiss_doing_timeout,
                                    RT_NULL,
                                    AIRKISS_DOING_TIMER,
                                    RT_TIMER_FLAG_SOFT_TIMER | RT_TIMER_FLAG_ONE_SHOT);
    if (!g_doing_timer)
    {
        rt_kprintf("Create airkiss doing timeout timer failed \n");
        goto _exit;
    }

    g_cfg_done_sem = rt_sem_create("tlink", 0, RT_IPC_FLAG_FIFO);
    if (!g_cfg_done_sem)
    {
        rt_kprintf("Create  airkiss config done sem failed! \n");
        goto _exit;
    }

    ak_contex = (airkiss_context_t *)rt_malloc(sizeof(airkiss_context_t));
    if (!ak_contex)
    {
        rt_kprintf("Malloc memory for airkiss context \n");
        goto _exit;
    }

    result = airkiss_init(ak_contex, &ak_conf);
    if (result != RT_EOK)
    {
        rt_kprintf("Airkiss init failed!!\r\n");
        goto _exit;
    }

    AIRKISS_PRINTF("Airkiss version: %s\r\n", airkiss_version());

    g_wlan_device = (struct rt_wlan_device *)rt_device_find(WIFI_DEVICE_STA_NAME);
    if (g_wlan_device == RT_NULL)
    {
        rt_kprintf("Device not found\n");
        return;
    }

    g_current_channel = 1;
    rt_wlan_set_channel(g_wlan_device, g_current_channel);
    rt_wlan_set_monitor_callback(g_wlan_device, airkiss_monitor_callback);
    rt_wlan_cfg_monitor(g_wlan_device, WIFI_MONITOR_START);

    rt_timer_start(g_switch_timer);

    if (rt_sem_take(g_cfg_done_sem, rt_tick_from_millisecond(1000 * 90)) != RT_EOK)
    {
        AIRKISS_PRINTF("Wait semaphore timeout \n");
    }
    if (airkiss_recv_ret == AIRKISS_STATUS_COMPLETE)
    {
        int8_t err;
        int8_t tick = 0;
        airkiss_result_t result;

        err = airkiss_get_result(ak_contex, &result);
        if (err == 0)
        {
            AIRKISS_PRINTF("airkiss_get_result() ok!\n");
            AIRKISS_PRINTF(" ssid = %s \n pwd = %s \n, ssid_length = %d \n pwd_length = %d \n, random = 0x%02x\r\n",
                           result.ssid, result.pwd, result.ssid_length, result.pwd_length, result.random);
        }

        rt_wlan_cfg_monitor(g_wlan_device, WIFI_MONITOR_STOP);
        rt_wlan_set_monitor_callback(g_wlan_device, RT_NULL);

        station_connect(result.ssid, result.pwd);
        do
        {
            tick ++;
            rt_thread_delay(rt_tick_from_millisecond(1000));
            if (tick >= 30)
            {
                rt_kprintf("GET IP Time Out!!! \n");
                goto _exit;
            }

        }
        while (!get_wifi_status(g_wlan_device->parent.netif));

        {
            rt_thread_t tid;

            tid = rt_thread_create("air_echo",
                                   airkiss_send_notification_thread, (void *)result.random,
                                   1536, RT_THREAD_PRIORITY_MAX - 3, 20);
            if (tid != RT_NULL)
            {
                rt_thread_startup(tid);
            }
        }
    }

_exit:
    if (g_switch_timer)
    {
        rt_timer_stop(g_switch_timer);
        rt_timer_delete(g_switch_timer);
    }
    if (g_doing_timer)
    {
        rt_timer_stop(g_doing_timer);
        rt_timer_delete(g_doing_timer);
    }
    if (ak_contex != RT_NULL)
    {
        rt_free(ak_contex);
        ak_contex = RT_NULL;
    }

    if (g_cfg_done_sem)
    {
        rt_sem_delete(g_cfg_done_sem);
        g_cfg_done_sem = 0;
    }
}

2.配置

(1)组件下载

下载smartconfig包(github下载),将smartconfig放在bsp/w60x/路径下,然后再rtconfig.h中修改,增加如下图所示宏定义:
在这里插入图片描述
(2)编译配置

在applications/2-net目录下新建一个文件夹:6-config_airkiss,然后需要修改aplications/SConscript脚本。

Import('RTT_ROOT')
Import('rtconfig')
from building import *

cwd = GetCurrentDir()
src  = Glob('2-net/6-config_airkiss/*.c')
CPPPATH = [cwd]

group = DefineGroup('Applications', src, depend = [''], CPPPATH = CPPPATH)

Return('group')

3.下载微信官方调试工具

下载地址:https://iot.weixin.qq.com/wiki/new/index.html?page=6-1
在这里插入图片描述

三、下载运行

在ENV控制台,输入scons命令,在build/Bin目录下生成rtthread_1M.FLS,

烧录运行后,操作APP配网:
在这里插入图片描述
调试串口信息如下:
在这里插入图片描述
调试串口信息中,收到路由器的密码,然后连接路由器成功,说明配网OK。

四、结语

1.总结:

本节完,实际操作过程中需要注意的地方有如下几点:

(1) 组件更新引入的问题

调用rt_smartconfig_start开始配网,成功后,进入smartconfig_result回调函数,不能直接发送UDP广播,因为回调函数中不宜做耗时过长操作,正确操作是创建发送线程,将需要广播的内容,通过传参方式传到线程中。

rt_smartconfig_start(SMARTCONFIG_TYPE_AIRKISS, SMARTCONFIG_ENCRYPT_NONE, RT_NULL, smartconfig_result);

2.资料获取

如您在使用过程中有任何问题,请加QQ群进一步交流。

QQ交流群:906015840 (备注:物联网项目交流)

公众号:物联网客栈,扫码关注,回复w600即可。
在这里插入图片描述
一叶孤沙出品:一沙一世界,一叶一菩提

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

物联网客栈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值