WiFi单品设备开发-基于认证模组
更新时间:2019-03-13 08:36:45
开发场景说明
本文的阅读对象是设备开发者,讲解设备开发者如何在通过阿里云IoT高级认证的WiFi模组上进行产品功能的开发,并将设备连接到飞燕平台。
不同的版本开发的接口可能存在差异,本文档基于AliOS Things 2.0版本进行编写。
开发过程
产品的开发过程如下图所示:
设备开发者需要安装AliOS Things的开发工具以及获取代码,配置运行的目标模组,然后进行产品功能的开发
开发环境设置
端侧开发者往往windows环境使用较多,接下来将介绍如何在安装了windows7的PC上搭建开发和烧写环境。
Windows下工具安装
体验者需要安装如下工具:
软件名称 | 功能描述 |
---|---|
python-2.7.14-x64.msi | python2.7环境 |
VSCodeSetup-x64-1.21.1.exe | 代码编辑工具 |
alios-studio.vsix | VSCode中的alios开发插件 |
cpptools.vsix | VSCode中C语言语法检测插件 |
Git-2.16.2-x64.exe | 控制台工具,用于执行aos-cube命令 |
FTDI_VCP_Driver.exe | FT232串口芯片驱动。用于模组烧写HF模组烧写 |
CP210x_Windows_Drivers | CP210X USB转串口芯片驱动,用于MK3080模组烧写 |
RDA Flashtest Tools | 固件烧写工具,用于HF-LPB130模组的固件烧写 |
ImageTool.exe | 固件烧写工具,用于MK3080模组的固件烧写 |
aos-cube | aos编译工具包。 |
完成软件安装后,打开vscode工具,在“文件”菜单中选择“首选项”->"设置",出现工具的配置文件,将如下代码拷贝到右侧的“用户设置”窗口中,将默认的终端设置为“Git bash”,请根据真实的bash安装地址调整bash.exe的路径。
{
"terminal.integrated.shell.windows": "C:\\Program Files\\Git\\bin\\bash.exe",
}
保存后关闭设置文件,在“查看”菜单中打开“集成终端”。可以看到vs code将bash作为其默认终端工具。
测试python安装情况,在终端中输入“python”命令,如果python安装正确,且环境变量设置正确,则可以看到如图的的输出
如果提示找不到命令,请将python2.7目录添加至环境变量中
点击桌面上的计算机图片,右击选择属性->选择高级系统设置;
选择环境变量按钮;
选择Path选项,点击"编辑",弹出窗口,在变量值框输入C:\Python27;
安装aos-cube
用 python 包管理器 pip
来安装 aos-cube
和相关的依赖包
在全局环境,以便于后续使用 AliOS Things Studio 进行开发。
在vs code的控制台中输入如下代码,安装aos-cube
$ pip install setuptools
$ pip install wheel
$ pip install aos-cube
在代码运行完成后在控制台中输入aos --version命令,验证安装是否成功。如果看到输出版本号,则表示安装成功。
获取AliOS Things代码
在vs code的控制台中,通过cd命令进入到自己希望放置代码的目录,然后执行如下命令,获取AliOS Things源码。
git clone https://github.com/alibaba/AliOS-Things.git
cd AliOS-Things
或通过https://github.com/alibaba/AliOS-Things.git连接访问github主页,通过zip方式下载源码,下载前请注意选择rel_2.0.0版本。
将下载的zip包解压后(或者通过git clone获得的源码目录),在VS code工具中打开AliOS Things文件夹。可以看到如此的画面。在vs code的终端中,通过cd命令进入与打开文件夹相同的路径。
验证aos是可以正常编译,下面是在庆科EMW3080模组上编译一个打印hello world程序示例:
aos make helloworld@mk3080
完成编译后,会在AliOS Things\out\helloworld@mk3080\binary目录下生成helloworld@mk3080.all.bin文件,该文件即为需要烧写的文件,用户可以将其烧写到3080模组上查看程序是否可以正常运行。
注:如果模组不是EMW3080,则替换上面命令中@之后的mk3080,用户可以查看目录board得知支持的模组。
将固件烧写到模组
在编译得到固件后,即可以将固件烧写到模组中执行,不同的模组烧写过程是不一样的,请联系模组厂商获取烧写工具以及烧写说明
在智能生活开放平台定义产品
下面的截图仅供参考,因为控制台的页面显示可能会发生变动。
创建产品
请登录“智能生活开放平台”、并参照文档“创建产品”进行产品创建。当创建产品时,将会跳出下面的页面:
如果设备开发者不熟悉智能生活开放平台的设备开发流程,建议客户首先创建一个“产品信息”中所属分类为“电工照明/灯”的产品进行熟悉
创建产品时:
“节点类型”请选择“设备”
“是否接入网关”选择“否”
“连网方式”选择“WiFi”
“数据格式”选择“ICA标准数据格式”
“使用ID2认证”选择“否”
功能定义
如果设备开发者不了解物模型,请阅读“物的模型”进行了解。
如果产品品类为灯,将显示下面的页面:
设备开发者可以在该页面上添加自定义的属性、服务、事件。
设备调试
在此步骤中,请选择选用的WiFi模组:
之后可以创建一个测试设备,请点击如下图所示的“新增测试设备”:
之后平台将为测试设备分配设备身份信息,如下图所示:
请将上图中的ProductKey、DeviceName、DeviceSecret复制到一个文件中待用。
人机交互
建议使能“使用公版App控制产品”,这样就可以使用智能生活开放平台提供的“平台公版App”对设备进行控制,如下图所示:
批量投产
设备开发结束之后才会投产,开发阶段请不要点击“完成开发”
设备端产品开发
设备开发分为两个大步骤:
产品功能实现
WiFi配网
产品模型开发
对于WiFi设备来说,首先需要通过WiFi配网来获得WiFi热点的SSID/密码,而WiFI配网的移植和调试是比较花时间的,所以产品开发的时候可以产品功能开发和WiFi配网并行实现。
下面的代码将配网的代码注释掉,让设备直接去连接一个指定的热点,然后连接到阿里云物联网平台,从而可以开始进行产品物模型功能的开发与调试。被修改的函数start_netmgr()位于文件AliOS-Things/app/example/linkkitapp/app_entry.c中
static void start_netmgr(void *p)
{
/*
* register event callback to detect event of AWSS
*/
iotx_event_regist_cb(linkkit_event_monitor);
//此处注释掉WiFi配网的事件回调处理函数以及WiFi配网功能的启动
#if 0
netmgr_start(true);
#endif
//下面的代码让设备直接去连接一个SSID为TSL_TEST,密码为12345678的热点
char *ssid = "TSL_TEST";
char *passwd = "12345678";
HAL_Awss_Connect_Ap(4000,ssid, passwd, 0, 0, NULL, 0);
aos_task_exit(0);
}
当设备连接到WiFi热点、获得一个IP地址之后会自动去连接阿里云IoT。
产品物模型描述设置
设备的物模型TSL描述位于AliOS-Things\app\example\linkkitapp\data\solo_tsl.data:
static const char TSL_STRING[] = "xxx";
设备开发者需要在飞燕平台去获取TSL文件,将其转换为字符串之后替换上面代码中TSL_STRING的内容xxx,可以参照“物的模型”中的说明去获取产品的TSL描述;如果觉得飞燕平台生成的TSL文件太大,也可以在物联网平台的控制台去获取精简版的TSL,可以参照下面的操作示例:
点击“查看物模型”,之后,将会跳出页面:
点击按钮“导出模型文件”可以将产品的物模型描述保存到本地的一个文件中。
产品开发者需要将该TSL文件中的JSON转换成一个C字符串后存放到数组TSL_STRING中,之后在函数linkkit_example()中将被使用:
if (!get_tsl_from_cloud) {
/*
* if get_tsl_from_cloud = 0, set default tsl [TSL_STRING]
* please modify TSL_STRING by the TSL's defined.
*/
linkkit_set_tsl(TSL_STRING, strlen(TSL_STRING));
}
注:C语言的字符串中如果出现引号,需要使用转义符对其进行转义,因此TSL文件中JSON对象的每个双引号都需要使用''对其进行转义。一个简单的办法就是使用文本编辑器将TSL文件打开之后,使用替换功能将双引号"替换为"
设备身份信息配置
在linkkit_example_solo.c的代码中设置了设备的身份信息,设备开发者需要将测试设备的信息对其进行替换:
#include "data/solo_tsl.data"
#define EVENT_ERROR_IDENTIFIER "Error"
#define EVENT_ERROR_OUTPUT_INFO_IDENTIFIER "ErrorCode"
// for demo only
#define PRODUCT_KEY "a15trrE4PqM"
#define PRODUCT_SECRET "4uZsr1uSnCwzhjPM"
#define DEVICE_NAME "IFn66CxbVIlOoOaI2cJy"
#define DEVICE_SECRET "qwvShyphCALzDy380ppcRnj0zNZFjc8S"
设备开发者将设备身份信息设置到程序之后,可以将代码进行编译并遵循模组商提供的烧写工具将固件写入模组,确保设备可以连接到阿里云物联网平台。如果模组提供串口打印输出,当模组连接到阿里云物联网平台后将会输出类似下面的信息提示:
如果模组可以正常连接阿里云物联网平台,在飞燕的商家后台可以看到这个设备已激活,以及设备连接到物联网平台的时间信息,如下图所示:
建议设备开发者先确保模组已经可以正常连接到阿里云物联网平台后,再继续进行下面物模型的实现。
注:在example中设备的身份信息是在代码中写死的,设备开发时需要通过HAL_GetProductKey()、HAL_GetDeviceName()、HAL_GetDeviceSecret()进行返回。
产品属性上报
产品的属性发生变化时,需要将变化后的数值上报到物联网平台。属性变化的检测以及上报是由设备开发者定义和实现的。
示例代码中对应的产品具有一个WIFI_Band的属性,类型为字符串格式;还具有一个WIFI_Channel的属性,类型为int32。在linkkit_example()函数中调用了函数post_property_wifi_status_once()用于上报WiFi的频段和信道,用户可以参考该代码实现产品的属性变化上报:
char *band = NULL;
int channel = 0;
...
band = wireless_info.band == 0 ? "2.4G" : "5G";
channel = wireless_info.channel;
...
//SDK内部有一个变量记录属性数值,下面的函数调用改变WIFI_Band变量的值
linkkit_set_value(linkkit_method_set_property_value, sample_ctx->thing, "WIFI_Band", band, NULL);
//将WIFI_Band的数值上报到物联网平台
linkkit_post_property(sample_ctx->thing,"WIFI_Band",post_property_cb);
linkkit_set_value(linkkit_method_set_property_value, sample_ctx->thing, "WIFI_Channel", &channel, NULL);
linkkit_post_property(sample_ctx->thing,"WIFI_Channel",post_property_cb);
产品事件上报
如果产品定义了事件,当事件发生时也需要向云端发送事件。事件的检测以及上报由设备开发者实现。
示例产品定义了一个标识符为“Error”的事件,该事件还有一个标识符为“ErrorCode”的输出参数。下面的代码示例描述了如何向物联网平台发送一个事件:
int trigger_event(sample_context_t *sample)
{
char event_output_identifier[64];
//设置事件的ID,
snprintf(event_output_identifier, sizeof(event_output_identifier), "%s.%s", EVENT_ERROR_IDENTIFIER, EVENT_ERROR_OUTPUT_INFO_IDENTIFIER);
int errorCode = 0;
//设置并记录事件outputData中的值
linkkit_set_value(linkkit_method_set_event_output_value,
sample->thing,
event_output_identifier,
&errorCode, NULL);
//将事件上报物联网平台
return linkkit_trigger_event(sample->thing, EVENT_ERROR_IDENTIFIER, post_property_cb);
}
产品回调函数处理
在函数linkkit_example()中的linkkit_ops定义了系统的各种事件处理函数,如下面的代码所示:
int linkkit_example()
{
sample_context_t sample_ctx = { 0 };
// int execution_time = 20;
int exit = 0;
unsigned long long now = 0;
unsigned long long prev_sec = 0;
int get_tsl_from_cloud =
0; /* the param of whether it is get tsl from cloud */
linkkit_ops_t linkkit_ops = {
.on_connect = on_connect, /* connect handler */
.on_disconnect = on_disconnect, /* disconnect handler */
.raw_data_arrived = raw_data_arrived, /* receive raw data handler */
.thing_create = thing_create, /* thing created handler */
.thing_enable = thing_enable, /* thing enabled handler */
.thing_disable = thing_disable, /* thing disabled handler */
.thing_call_service =
thing_call_service, /* self-defined service handler */
.thing_prop_changed = thing_prop_changed, /* property set handler */
.linkit_data_arrived =
linkit_data_arrived, /* transparent transmission data handler */
};
下面linkkit_ops的成员对应的函数进行说明:
- on_connect
被调用时机:该设备连接到阿里云物联网平台后该处理函数将被调用。用户可以在该函数中加入自己的业务逻辑,比如设备如果有一个LED灯显示系统状态,那么可以将该LED点亮表示系统已连接到阿里云物联网平台。
下面是示例代码:
#ifdef LOCAL_CONN_ENABLE
static int on_connect(void *ctx, int cloud)
#else
static int on_connect(void *ctx)
#endif
{
sample_context_t *sample_ctx = ctx;
#ifdef LOCAL_CONN_ENABLE
if (cloud) {
sample_ctx->cloud_connected = 1;
} else {
sample_ctx->local_connected = 1;
}
EXAMPLE_TRACE("%s is connected\n", cloud ? "cloud" : "local");
#else
sample_ctx->cloud_connected = 1;
EXAMPLE_TRACE("%s is connected\n", "cloud");
#endif
#if defined(OTA_ENABLED)
ota_service_init(NULL);
#endif
/* do user's connect process logical here. */
/* ............................... */
/* user's connect process logical complete */
设备上报给物联网平台的属性会被物联网平台存放起来,当手机APP从物联网平台查看设备状态时,物联网平台并不会实时到设备上查询设备的最新状态,而是直接将物联网平台记录的设备属性的最新值返回给手机APP。
因此为了避免物联网平台上存放的设备的属性数值与设备的不一致,设备连接物联网平台时建议将设备的属性的最新数值上报到云端;设备厂商如果不想每次设备连接到物联网平台时都将自己的所有属性的数值都上报物联网平台,也可以将上次上报到云端的属性的值在本地存放,如果与物联网平台建立连接时发现自己的某个属性并没有发生变化,对于属性数值未发生变化的属性无需上报到平台。
- on_disconnect
调用时机:当设备与云端的连接被断开时调用。该函数的示例代码如下:
#ifdef LOCAL_CONN_ENABLE
static int on_disconnect(void *ctx, int cloud)
#else
static int on_disconnect(void *ctx)
#endif
{
sample_context_t *sample_ctx = ctx;
#ifdef LOCAL_CONN_ENABLE
if (cloud) {
sample_ctx->cloud_connected = 0;
} else {
sample_ctx->local_connected = 0;
}
EXAMPLE_TRACE("%s is disconnect\n", cloud ? "cloud" : "local");
#else
sample_ctx->cloud_connected = 0;
EXAMPLE_TRACE("%s is disconnect\n", "cloud");
#endif
/* do user's disconnect process logical here. */
/* ............................... */
/* user's disconnect process logical complete */
return 0;
}
设备开发者可以在该函数中加入自己希望添加的业务逻辑。比如,如果设备有一个LED指示灯,可以将其设置为红色,表示设备与物联网平台的连接断开了。
- raw_data_arrived
该函数已弃用
- thing_create
被调用时机:当设备资源创建完成时被调用,获取设备的thing_id,此后可根据此thing_id对设备进行属性上报、事件上报等操作。一般来说,用户无需在这里嵌入代码。
static int thing_create(const void *thing_id, void *ctx)
{
sample_context_t *sample_ctx = ctx;
EXAMPLE_TRACE("new thing@%p created.\n", thing_id);
sample_ctx->thing = thing_id;
/* do user's thing create process logical here. */
/* ............................... */
/* user's thing create process logical complete */
return 0;
}
- thing_enable
该函数已弃用
- thing_disable
该函数已弃用
- thing_call_service
被调用时机:当设备收到来自云端的服务请求时,该函数被调用。
在示例产品中定义了下面的服务:
标识符: Custom
调用方式: 异步
输入参数: {标识符: transparency, 数据类型: int32, 取值范围: 0 ~ 100}
输出参数: {标识符: Contrastratio, 数据类型: int32, 取值范围: 0 ~ 100}
下面是处理代码示例:
#ifdef RRPC_ENABLED
static int handle_service_custom(sample_context_t *_sample_ctx,
const void *thing,
const char *service_identifier, int request_id,
int rrpc)
#else
static int handle_service_custom(sample_context_t *_sample_ctx,
const void *thing,
const char *service_identifier, int request_id)
#endif /* RRPC_ENABLED */
{
char identifier[128] = { 0 };
/*
* please follow TSL modify the value type
*/
int transparency_value;
int contrastratio_value;
/*
* get iutput value.
* compare the service identifier
* please follow user's TSL modify the "transparency".
*/
snprintf(identifier, sizeof(identifier), "%s.%s", service_identifier,
"transparency");
linkkit_get_value(linkkit_method_get_service_input_value, thing, identifier,
&transparency_value, NULL);
EXAMPLE_TRACE("identifier: %s value is %d.\n", identifier,
transparency_value);
//用户解析出服务的内容后,需要执行相应的操作
/*
* set output value according to user's process result.
* example rule: Contrastratio will changed by transparency.
*/
/* do user's service process logical here. */
/* ............................... */
/* user's service process logical complete */
//发送服务应答
#ifdef RRPC_ENABLED
linkkit_answer_service(thing, service_identifier, request_id, 200, rrpc);
#else
linkkit_answer_service(thing, service_identifier, request_id, 200);
#endif /* RRPC_ENABLED */
- thing_prop_changed
被调用时机:从云端控制台进行属性设置操作时,该函数被调用
static int thing_prop_changed(const void *thing_id, const char *property,
void *ctx)
函数入参说明:
thing_id:设备标识,在thing_create函数中获得的设备标识
property:属性ID,表示被设置的属性ID
ctx:用户使用linkkit_post_property时自己传入的上下文
下面的示例代码演示如何获取设置的属性值:
char *value_str = NULL;
char property_buf[64] = {0};
int response_id = -1;
...
//解析属性
if (strstr(property, "HSVColor") != 0) {
int hue, saturation, value;
/* generate property identifier HSVColor.Hue */
snprintf(property_buf, sizeof(property_buf), "%s.%s", property, "Hue");
/* get value by linkkit_get_value */
//获取参数的数值
linkkit_get_value(linkkit_method_get_property_value, thing_id,
property_buf, &hue, &value_str);
//用户解析出属性的数值之后,在此处做处理
//处理结束之后,释放资源
if (value_str) {
free(value_str);
value_str = NULL;
}
}
...
// 示例代码默认设置成功,然后将属性的值上报云端
response_id = linkkit_post_property(thing_id, property, post_property_cb);
return 0;
- linkit_data_arrived
该函数已弃用
主循环处理
在linkkit_example()中存在一个循环,代码如下所示:
while (!linkkit_is_try_leave()) {
/*
* if linkkit is support Multi-thread, the linkkit_dispatch and
* linkkit_yield with callback by linkkit, else it need user to call
* these function to received data.
*/
#if (CONFIG_SDK_THREAD_COST == 0)
linkkit_yield(100);
linkkit_dispatch();
#else
HAL_SleepMs(100);
#endif /* CONFIG_SDK_THREAD_COST */
now = uptime_sec();
if (prev_sec == now) {
continue;
}
...
其中还包含了下面一段演示代码,用于上报WiFi的状态、上报所有的属性、上报事件等,在实际产品开发时需要将其修改为设备商自己的逻辑:
#ifdef POST_WIFI_STATUS
if (now % 10 == 0) {
post_property_wifi_status_once(&sample_ctx);
}
#endif
if (now % 30 == 0 && is_active(&sample_ctx)) {
post_all_prop(&sample_ctx);
}
if (now % 45 == 0 && is_active(&sample_ctx)) {
trigger_event(&sample_ctx);
}
#ifdef EXTENDED_INFO_ENABLED
if (now % 50 == 0 && is_active(&sample_ctx)) {
trigger_deviceinfo(&sample_ctx);
}
#endif
设备商完成自己物模型功能的代码编写之后,可以将固件编译出来并根据相关模组的烧写方法把固件烧写到模组上进行功能验证和调试。
WiFi配网开发
在开发WiFi配网的时候,请确保去除前面章节开发产品物模型时对start_netmgr()的修改:
static void start_netmgr(void *p)
{
/*
* register event callback to detect event of AWSS
*/
iotx_event_regist_cb(linkkit_event_monitor);
netmgr_start(true);
aos_task_exit(0);
}
WiFi配网事件的处理
start_netmgr()函数中注册了事件回调处理函数,设备商可以自己在linkkit_event_monitor()函数中增加对各种事件的处理,比如如果设备有一个LED来显示配网状态,那么可以在不同的事件发生时更新LED的显示来让用户了解设备的配网状态。
WiFi配网功能初始化之后,为了安全着想,设备并不会默认接受来自手机APP或者其它配网设备的配网信息,设备厂商需要设计一个按键来让WiFi设备启动配网(也可以复用已有按键,比如让一个已有按键长按3秒的方式来启动配网),配网按键被触发之后调用的函数为 int awss_config_press()。
如何检测配网按键被触发,以及编写相应的逻辑需要由模组商自行设计与实现。更多的WiFi配网的描述可参见配网开发文档。