前言
本文研究如何使用微信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即可。
一叶孤沙出品:一沙一世界,一叶一菩提