草庐IT

OpenHarmony轻量化系统ANY技术实践

X丶昕雪 2023-03-28 原文

想了解更多关于开源的内容,请访问:

​51CTO 开源基础软件社区​

​https://ost.51cto.com​

概述

ANY功能是一种华为私有的短数据通信功能,允许处于同一信道的2个Wi-Fi设备进行直接的点对点无连接通信。

ANY可以应用于智能开关控制灯泡、传感器数据采集、遥控器控制家用电器等无线控制场景。

ANY功能特点

  • 每个设备可以选择一个接口(例如:wlan0或ap0)用于ANY报文的收发。
  • ANY报文采用接口当前所在信道进行收发,和通信对端需要处于同一信道。
  • 单个ANY报文最多可以支持250byte的用户层数据。
  • 单个ANY设备最多支持同时和16个ANY对端设备进行通信,其中最多允许和6个对端进行加密通信。
  • ANY设备可以收发ANY单播报文ANY广播报文,不支持组播报文。
  • ANY设备可以扫描发现附近的其他ANY设备。

环境

  • OpenHarmony-3.1
  • 润和hispark_pegasus Hi3861开发板
  • DevEco Device Tool
  • 串口调试助手

总体流程

总体流程为:

  1. ANY功能初始化。
  2. 注册收发回调函数。
  3. 创建收发线程T1处理收发回调(将收发回调函数用此线程完成,避免长期占用主线程)。
  4. 进行ANY设备扫描。
  5. 调用hi_wifi_any_deinit,去初始化ANY。

1、初始化

首先设备需要完成STA或者SoftAP的初始化;再以此作为ANY收发通信的接口,初始化ANY功能。初始化ANY功能之后,ANY会采用其所在的信道进行通信,并采用MAC地址作为发送接收的地址。

  • 对于SoftAP模式下,所在的信道就是创建的时候指定的信道。
  • 对于STA模式,若已连接到了SoftAP,所在信道为该SoftAP指定的信道。
  • 对于STA模式,若未连接,可以采用设置信道的API函数hi_wifi_set_channel给STA指定一个信道。
对于ANY功能初始化,步骤为:启动STA或SoftAP调用​hi_wifi_any_init,选择STA接口(wlan0)或AP接口(ap0)作为ANY通信接口。

调用hi_wifi_any_deinit,去初始化ANY(关闭WiFi会自动关闭ANY)。
对于初始化函数,返回值为0即执行成功,1则为执行失败。

/* STA接口的信息 */
hi_char ifname[16 + 1] = {0};
hi_s32 len = sizeof(ifname);
/*WIFI_STA功能启动*/
hi_wifi_sta_start(ifname, &len);
/* 初始化并注册发送完成和接收回调函数 */
hi_wifi_any_init(ifname);
/* AP接口的信息 */
hi_wifi_softap_config hapd_conf = {
"my_wifi", "", 1, 0, HI_WIFI_SECURITY_OPEN, HI_WIFI_PARIWISE_UNKNOWN};
hi_char ifname[16 + 1] = {0}; /* 创建的SoftAP接口名 */
hi_s32 len = sizeof(ifname); /* SoftAP接口名长度 */
struct netif *netif_p = HI_NULL;
ip4_addr_t st_gw;
ip4_addr_t st_ipaddr;

ip4_addr_t st_netmask;
/* 这里用户配置自己的网关、IP、掩码 */
IP4_ADDR(&st_gw, 0, 0, 0, 0);
IP4_ADDR(&st_ipaddr, 0, 0, 0, 0);
IP4_ADDR(&st_netmask, 0, 0, 0, 0);
hi_u8 ap_msg[] = "msg from my_wifi";
/* 配置SoftAP网络参数,beacon周期修改为200ms */
hi_wifi_softap_set_beacon_period(200);
/* 启动SoftAP接口 */
hi_wifi_softap_start(&hapd_conf, ifname, &len);
/* 配置DHCP服务器 */
netif_p = netif_find(ifname);
if (netif_p == HI_NULL)
{
(hi_void) hi_wifi_softap_stop();
return HISI_FAIL;
}
if (netifapi_netif_set_addr(netif_p, &st_ipaddr, &st_netmask, &st_gw) != HISI_OK)
{
(hi_void) hi_wifi_softap_stop();
return HISI_FAIL;
}
if (netifapi_dhcps_start(netif_p, NULL, 0) != HISI_OK)
{
(hi_void) hi_wifi_softap_stop();
return HISI_FAIL;
}
/* 初始化并注册发送完成和接收回调函数 */
hi_wifi_any_init(ifname);

2、注册ANY收发回调函数

ANY设备能够与同信道的其他ANY设备进行通信。其中单个ANY报文最多支持250Byte的用户数据。支持加密通信或不加密通信,加密通信需要要求双方提前配置同样的16Byte长度密钥。

在通信前需要确保:ANY功能初始化,以及通信双方处于同一个信道。

接受和发送回调函数同样是运行于驱动线程,建议新建一个线程T1(第三步骤)处理收发报文,所以回调函数内的任务只是将msg数据通过消息队列传到收发线程T1。

  1. 实现接收回调函数&& 发送完成回调函数,hi_wifi_any_recv_cb​ && hi_wifi_any_send_complete_cb。
  2. 回调函数内调用write_any_msg函数,msg数据将会在收发线程T1里面处理。
  3. 调用hi_wifi_any_set_callback向驱动注​册回调函数,其中发送回调函数反馈发送结果(成功为1),若不需要该函数,注册时可以设置为HI_NULL。
ANY只在当前信道通信,想要与其他信道设备通信,需要下调用切信道API

一个ANY设备支持和16个对端通信,最多6个对端加密通信,并且需要双方配置相同密钥。

接收回调函数的数据内存由驱动自行管理。

hi_wifi_any_set_callback(wifi_any_send_cb, wifi_any_recv_cb);
/*发送完成回调函数*/
void wifi_any_send_cb(unsigned char *mac, unsigned char status, unsigned char seqnum)
{
any_msg_stru msg = {0};
memcpy_s(msg.mac, sizeof(msg.mac), mac, sizeof(msg.mac));
msg.type = ANY_SEND_COMPLETE_CALLBACK;
msg.status = status;
msg.seqnum = seqnum;

//调用write_any_msg函数
write_any_msg(&msg);
}
/* 接收回调函数*/
void wifi_any_recv_cb(unsigned char *mac, unsigned char *data, unsigned short len, unsigned char seqnum)
{
any_msg_stru msg = {0};
msg.type = ANY_RECV_CALLBACK;
memcpy_s(msg.mac, sizeof(msg.mac), mac, sizeof(msg.mac));
msg.len = len;
msg.seqnum = seqnum;
msg.data = (unsigned char *)hi_malloc(HI_MOD_ID_WIFI_DRV, len);
if (msg.data == NULL)
return;
if (memcpy_s(msg.data, len, data, len) != EOK)
{
hi_free(HI_MOD_ID_WIFI_DRV, msg.data);
return;
}
//调用write_any_msg函数
if (write_any_msg(&msg) != HI_ERR_SUCCESS)
hi_free(HI_MOD_ID_WIFI_DRV, msg.data);
}
/* 向收发线程T1发送msg */
hi_u32 write_any_msg(any_msg_stru *msg)
{
hi_u32 ret;
if ((g_any_msg_queue == 0) || (g_task_running == 0))
{
printf("msg queue or task is not working!\r\n");
return HI_ERR_FAILURE;
}
//将msg消息发送到消息队列
ret = hi_msg_queue_send(g_any_msg_queue, msg, 0, sizeof(any_msg_stru));
return ret;
}

3、处理子线程T1

由于接收回调、发送回调、扫描回调都不易过多占用主线程的运行,所以我们需要创建一个新的线程用于实现接收回调、发送回调、扫描回调的活。
创建线程&&线程实现:

/*创建处理子线程T1*/
hi_u32 any_start_callback_task(hi_void)
{
hi_u32 ret;
if (g_any_msg_task_id != 0)
{
return HI_ERR_FAILURE;
}
hi_task_attr task_init = {0};
task_init.task_prio = ANY_TASK_PRIORITY;
task_init.stack_size = ANY_TASK_STACK_SIZE;
task_init.task_name = ANY_TASK_NAME;
g_task_running = 1;
ret = hi_task_create(&g_any_msg_task_id, &task_init, handle_any_msg, HI_NULL);
if (ret != HI_ERR_SUCCESS)
{
printf("create any msg task ret:%d\r\n", ret);
return ret;
}

printf("create any msg task success!\r\n");
return ret;
}
hi_u32 any_destory_callback_task(hi_void)
{
printf("destory any callback task\r\n");
g_task_running = 0;
return HI_ERR_FAILURE;
}
/*处理子线程T1*/
static hi_void *handle_any_msg(hi_void *data)
{
(hi_void) data;
int ret;
any_msg_stru msg = {0};
if (g_any_msg_queue == 0)
{
ret = hi_msg_queue_create(&g_any_msg_queue, ANY_MSG_QUEUE_MAX_LEN, sizeof(any_msg_stru));
if (ret != HI_ERR_SUCCESS)
{
printf("create any message queue ret:%d\r\n", ret);
g_any_msg_task_id = 0;
return HI_NULL;
}
}
while (1)
{
hi_u32 msg_size = sizeof(any_msg_stru);
ret = hi_msg_queue_wait(g_any_msg_queue, (hi_pvoid)&msg, ANY_TASK_SLEEP_TIME,&msg_size);
if (ret == HI_ERR_SUCCESS)
{

switch (msg.type)
{
case ANY_SCAN_CALLBACK:
/*扫描回调*/
wifi_any_scan_success_proc(msg.mac, msg.channel);
break;
case ANY_RECV_CALLBACK:
/*接收回调*/
wifi_any_recv_proc(msg.mac, msg.data, msg.len, msg.seqnum);
hi_free(HI_MOD_ID_WIFI_DRV, msg.data);
break;
case ANY_SEND_COMPLETE_CALLBACK:
/*发送完成回调*/
wifi_any_send_complete_proc(msg.mac, msg.status, msg.seqnum);
break;
default:
break;
}
}
else
{
if (g_task_running == 0)
{
break;
}
}
}
g_any_msg_task_id = 0;
hi_u8 trycount = 3;
while (trycount > 0)
{
if (hi_msg_queue_delete(g_any_msg_queue) == HI_ERR_SUCCESS)
{
g_any_msg_queue = 0;
return HI_NULL;
}
trycount--;
}
printf("delete any msg queue failed!\r\n");
return HI_NULL;
}
真正的接收回调、发送回调、扫描回调(可参考文章末尾的源码):

/* 接收回调 */
void wifi_any_recv_proc(const unsigned char *mac, unsigned char *data, unsigned short len,unsigned char seqnum)
{}
/* 发送完成回调 */
void wifi_any_send_complete_proc(const unsigned char *mac, unsigned char status, unsigned char seqnum)
{}
/* 扫描回调 */
void wifi_any_scan_success_proc(const unsigned char *mac, unsigned char channel)
{}

4、ANY设备扫描

完成ANY初始化后,STA端可以进行设备的扫描:

  1. 首先实现hi_wifi_any_scan_result_cb扫描回调函数,用于处理扫描完成之后的结果。对于该回调函数,驱动传入的输入参数为发现的ANY设备的指针数组和数组元素个数,每个元素指向一个发现的ANY设备相关信息。
  2. 调用hi_wifi_any_discover_peer,向驱动注册回调函数,并**启动一次ANY扫描。
扫描过程中,发现的设备是SoftAP,则设备信息会包括SSID信息,STA则为空字符串。

回调函数运行于驱动线程,不能阻塞或长时间​等待,使用第三步骤的处理子线程T1进行处理。

单次扫描最多通过回调函数返回32个对端设备信息,回调函数传入的数组内存由驱动自行管理,在回调函数中不应释放。

//g_find_any_device初始值为0,完成扫描回调函数退出whil循环
while (!g_find_any_device)
{
/* wifi_any_scan_result_cb : 扫描回调函数 */
hi_wifi_any_discover_peer(wifi_any_scan_result_cb);
hi_sleep(2000); //延时2s
}
/*扫描回调函数*/
void wifi_any_scan_result_cb(hi_wifi_any_device *devices[], unsigned char num)
{
unsigned char target_ssid[] = "my_wifi";
any_msg_stru msg = {0};
unsigned char loop;
if ((devices == NULL) || (num == 0))
{
printf("Total scanned ANY dev num: 0\r\n");
return;
}

for (loop = 0; (loop < num) && (devices[loop] != NULL); loop++)
{
if (strcmp((char *)devices[loop]->ssid, (char *)target_ssid) != 0)
{
continue;
}
g_find_any_device = 1;
msg.type = ANY_SCAN_CALLBACK;
msg.channel = devices[loop]->channel;
memcpy_s(msg.mac, WIFI_ANY_MAC_LEN, devices[loop]->bssid, WIFI_ANY_MAC_LEN);
if (write_any_msg(&msg) != HI_ERR_SUCCESS)
{
printf("write scan result failed\r\n");
}
break;
}
printf("Total scanned ANY dev num: %d\r\n", num);
return;
}

5、加密

ANY设备支持加密通信,要求通信双方采用同样的16byte长度密钥进行加解密,一个密钥对应一个对端MAC地址,即采用该密钥加解密来自该MAC地址的ANY报文。用户应根据产品应用场景确定ANY设备之间如何产生和共享同样的密钥。

设备1和设备2的配对协商消息均是采用ANY报文。协商请求消息中携带了随机数R1,协商应答消息携带了随机数R2和Cookie值。参与协商的2个设备需要采用同样的预共享密钥PSK,设备获取到随机数R1和R2之后结合PSK生成一个临时密钥用于加密密钥协商消息。设备1采用该临时密钥加密密钥请求消息,密钥请求消息中包括来自设备2的Cookie和随机生成的密钥因子K1。设备2采用临时密钥解密消息之后验证Cookie是否正确,验证通过则回复密钥应答消息。密钥应答消息中包含随机生成密钥因子K2,并采用临时密钥加密。双方完成上述交互之后,通过随机数R1和R2、密钥因子K1和K2采用同样的算法生成用于通信的最终的密钥。​

6、发送函数

向指定MAC地址的设备发送ANY数据。

int hi_wifi_any_send(const unsigned char *mac, unsigned char mac_len, unsigned char *data,unsigned short data_len, unsigned char seq);
  • mac:6字节长度目的MAC地址, 可为单播或者广播地址, 不支持组播地址。
  • mac_len:MAC地址长度, 需为6字节。
  • data:待发送数据的缓存地址。
  • len:待发送的数据长度, 最大为250字节。
  • seqnum:待发送的ANY帧的序列号,范围0-255。

效果

此样例连接后互相发送信息。

这里可以看到AP端的mac地址为0x8e,STA端的mac地址为0x32。

STA端扫描发现ANY设备:

互相发送信息:

文章相关附件可以点击下面的原文链接前往下载:

 https://ost.51cto.com/resource/2570。

想了解更多关于开源的内容,请访问:

​51CTO 开源基础软件社区​

​https://ost.51cto.com​

有关OpenHarmony轻量化系统ANY技术实践的更多相关文章

  1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  2. ruby-on-rails - Ruby on Rails : . 常量化 : wrong constant name error? - 2

    我正在使用这个:4.times{|i|assert_not_equal("content#{i+2}".constantize,object.first_content)}我之前声明过局部变量content1content2content3content4content5我得到的错误NameError:wrongconstantnamecontent2这个错误是什么意思?我很确定我想要content2=\ 最佳答案 你必须用一个大字母来调用ruby​​常量:Content2而不是content2。Aconstantnamestart

  3. 叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践 - 2

    导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵

  4. 电脑0x0000001A蓝屏错误怎么U盘重装系统教学 - 2

      电脑0x0000001A蓝屏错误怎么U盘重装系统教学分享。有用户电脑开机之后遇到了系统蓝屏的情况。系统蓝屏问题很多时候都是系统bug,只有通过重装系统来进行解决。那么蓝屏问题如何通过U盘重装新系统来解决呢?来看看以下的详细操作方法教学吧。  准备工作:  1、U盘一个(尽量使用8G以上的U盘)。  2、一台正常联网可使用的电脑。  3、ghost或ISO系统镜像文件(Win10系统下载_Win10专业版_windows10正式版下载-系统之家)。  4、在本页面下载U盘启动盘制作工具:系统之家U盘启动工具。  U盘启动盘制作步骤:  注意:制作期间,U盘会被格式化,因此U盘中的重要文件请注

  5. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  6. Unity 热更新技术 | (三) Lua语言基本介绍及下载安装 - 2

    ?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------

  7. MIMO-OFDM无线通信技术及MATLAB实现(1)无线信道:传播和衰落 - 2

     MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO

  8. kvm虚拟机安装centos7基于ubuntu20.04系统 - 2

    需求:要创建虚拟机,就需要给他提供一个虚拟的磁盘,我们就在/opt目录下创建一个10G大小的raw格式的虚拟磁盘CentOS-7-x86_64.raw命令格式:qemu-imgcreate-f磁盘格式磁盘名称磁盘大小qemu-imgcreate-f磁盘格式-o?1.创建磁盘qemu-imgcreate-fraw/opt/CentOS-7-x86_64.raw10G执行效果#ls/opt/CentOS-7-x86_64.raw2.安装虚拟机使用virt-install命令,基于我们提供的系统镜像和虚拟磁盘来创建一个虚拟机,另外在创建虚拟机之前,提前打开vnc客户端,在创建虚拟机的时候,通过vnc

  9. ruby-on-rails - Rails 中同一个类的多个关联的最佳实践? - 2

    我认为我的问题最好用一个例子来描述。假设我有一个名为“Thing”的简单模型,它有一些简单数据类型的属性。像...Thing-foo:string-goo:string-bar:int这并不难。数据库表将包含具有这三个属性的三列,我可以使用@thing.foo或@thing.bar之类的东西访问它们。但我要解决的问题是当“foo”或“goo”不再包含在简单数据类型中时会发生什么?假设foo和goo代表相同类型的对象。也就是说,它们都是“Whazit”的实例,只是数据不同。所以现在事情可能看起来像这样......Thing-bar:int但是现在有一个新的模型叫做“Whazit”,看起来

  10. ruby-on-rails - 向 Rails 3 添加 Ruby 扩展方法的最佳实践? - 2

    我有一个要在我的Rails3项目中使用的数组扩展方法。它应该住在哪里?我有一个应用程序/类,我最初把它放在(array_extensions.rb)中,在我的config/application.rb中我加载路径:config.autoload_paths+=%W(#{Rails.root}/应用程序/类)。但是,当我转到railsconsole时,未加载扩展。是否有一个预定义的位置可以放置我的Rails3扩展方法?或者,一种预先定义的方式来添加它们?我知道Rails有自己的数组扩展方法。我应该将我的添加到active_support/core_ext/array/conversion

随机推荐