移植说明:本文档描述在MTK2503/MTK6261芯片+Nucleus操作系统的硬件平台上,使用异步通信模式将SDK集成到目标平台,集成时使用MQTT Topic方式与阿里云物联网平台进行通信。

开发环境:Windows XP开发主机及MTK的USB转串口驱动,ARM RVCT3.1/Perl构建环境,FlashTool烧录工具,Catcher调试工具

运行顶级目录下的config.bat脚本,在出现的界面关闭FEATURE_DEVICE_MODEL_ENABLED,然后进入 MQTT Configuations子菜单

前面已经介绍过,FEATURE_ASYNC_PROTOCOL_STACK是专为异步TCP/IP协议栈开发的特性,所以这里选中它,打开

保存后退出,运行顶级目录下的extract.bat脚本,根据这种配置抽取移植所需要的源文件到output目录下

对接HAL接口

上图中可以看到extract.bat已经将需要对接的HAL接口列出并自动整理到output/eng/wrappers/wrapper.c, 用户现在要根据系统的情况将这个文件中的空函数都进行实现

可以直接使用SDK源码目录wrappers/os/nucleus中的参考代码,根据自己的实际需要做一些调整

将被抽取的文件和实现好的 wrapper.c 加入构建

拷贝output/eng下所有目录及文件到Nucleus的编译环境中,确保编译通过,可以将C-SDK作为一个库集成到原有系统中

参考如下源码编写应用调用C-SDK

以下是我们开发过程中经过实际验证的源文件,您可参考编写自己的应用程序调用被集成的C-SDK

例程讲解

以下对示例的aliMqttTask.c运行流程做一些讲解

监听系统事件,构造应用程序入口

监听Nucleus中系统事件EVT_ID_SRV_NW_INFO_SERVICE_AVAILABILITY_CHANGED,当GPRS连接成功,系统会触发该事件

/* 系统事件回调函数 */
mmi_ret srv_nw_name_main_evt_hdlr(mmi_event_struct *event)
{
    ...
switch (event->evt_id)
    ...
    case EVT_ID_SRV_NW_INFO_SERVICE_AVAILABILITY_CHANGED:
        ret = srv_nw_name_handle_service_availability_changed(event);
    ...
            

把事件传递给状态机处理函数:aliyun_network_changed()

static mmi_ret srv_nw_name_handle_service_availability_changed(
mmi_event_struct *event)
{
    ...
    #if defined(__LL_ALIYUN_SUPPORT__)
    /* 进入业务回调函数,处理系统事件 */
    aliyun_network_changed((kal_int32)evt->new_status);
    #endif /*__LL_MYCOMMON_SUPPORT__*/
    ...
}
            

如果aliyun_network_changed()发现事件是表达GPRS连接已就绪,则启动应用程序ali_app_start_fun()

#define ALIYUN_NETWORK_GPRS_OK 3
void aliyun_network_changed(kal_int8 status)
{
    ...
    /* srv_nw_info_location_info_struct info; */
    DEBUG_TRACE("service_changedstatus.status=%d",status);

    g_gprs_status = status;
    /* 该事件表示GPRS连接成功 */
    if (g_gprs_status == ALIYUN_NETWORK_GPRS_OK) 
    {
        if(g_sim_init_complete == KAL_FALSE)
        {
            g_sim_init_complete = KAL_TRUE;
            StopTimer(ALIYUN_START_TIMER);
            /* 这里开始执行业务逻辑 */
            StartTimer(ALIYUN_START_TIMER,1000*20,ali_app_start_fun);

        }
    }
    ...
}
            

同时,我们也构造了一个专用于MQTT通信的任务:mqtt_task_main()

kal_bool mqtt_create(comptask_handler_struct **handle)
{
    static const comptask_handler_struct mqtt_handler_info =
    {   
        mqtt_task_main,  /* task entry function */
        mqtt_task_init,  /* task initialization function */
        NULL,           /* task configuration function */
        NULL, /* task reset handler */
        NULL,           /* task termination handler */
    };  

    *handle = (comptask_handler_struct*)&mqtt_handler_info;

    return KAL_TRUE;
}
            

GPRS连接成功,计算签名信息,并解析签名中的服务器域名为IP

ali_app_start_fun()首先会调用SDK提供的接口IOT_MQTT_Sign()获取签名信息,其中含有服务器域名

static void ali_app_start_fun(void)
{
    uint32_t res = 0;
    iotx_dev_meta_info_t meta;
    ...
    res = IOT_Sign_MQTT(IOTX_CLOUD_REGION_SHANGHAI,&meta,&g_mqtt_signout);
    if (res == 0) {
        DEBUG_TRACE("signout.hostname: %s",g_mqtt_signout.hostname);
        DEBUG_TRACE("signout.port    : %d",g_mqtt_signout.port);
        DEBUG_TRACE("signout.clientid: %s",g_mqtt_signout.clientid);
        DEBUG_TRACE("signout.username: %s",g_mqtt_signout.username);
        DEBUG_TRACE("signout.password: %s",g_mqtt_signout.password);
            

Nucleus的系统接口soc_gethostbyname()把域名解析成IP地址

...
        ...
        res = soc_gethostbyname(KAL_FALSE,MOD_MQTT,g_ali_request_id,g_mqtt_signout.hostname,
                 (kal_uint8 *)addr_buff, &addr_len,0,g_ali_nwk_account_id);
            

若域名成功解析,则调用SDK提供的接口IOT_MQTT_Construct()发起TCP连接

if (res >= SOC_SUCCESS)             // success
        {   
            DEBUG_TRACE("ali task socket_host_by_name SOC_SUCCESS");
            if(addr_len !=0 && addr_len<MAX_SOCK_ADDR_LEN)
            {   
                //connect....
                sprintf(ipaddr,"%d.%d.%d.%d",addr_buff[0],addr_buff[1],addr_buff[2],addr_buff[3]);
                DEBUG_TRACE("ipaddr: %s[%d.%d.%d.%d]",ipaddr,addr_buff[0],addr_buff[1],addr_buff[2],addr_buff[3]);
                HAL_Free(g_mqtt_signout.hostname);
                g_mqtt_signout.hostname = HAL_Malloc(strlen(ipaddr) + 1);
                if (g_mqtt_signout.hostname == NULL) {
                    DEBUG_TRACE("HAL_Malloc failed");
                    return;
                }
                memset(g_mqtt_signout.hostname,0,strlen(ipaddr) + 1);
                memcpy(g_mqtt_signout.hostname,ipaddr,strlen(ipaddr));

                DEBUG_TRACE("g_mqtt_signout.hostname: %s",g_mqtt_signout.hostname);

                memset(&mqtt_params,0,sizeof(iotx_mqtt_param_t));

                mqtt_params.port = g_mqtt_signout.port;
                mqtt_params.host = g_mqtt_signout.hostname;
                mqtt_params.client_id = g_mqtt_signout.clientid;
                mqtt_params.username = g_mqtt_signout.username;
                mqtt_params.password = g_mqtt_signout.password;

                mqtt_params.request_timeout_ms = 2000;
                mqtt_params.clean_session = 0;
                mqtt_params.keepalive_interval_ms = 60000;
                mqtt_params.read_buf_size = 1024;
                mqtt_params.write_buf_size = 1024;

                mqtt_params.handle_event.h_fp = example_event_handle;
                mqtt_params.handle_event.pcontext = NULL;

                g_mqtt_handle = IOT_MQTT_Construct(&mqtt_params);
                if (g_mqtt_handle != NULL){
                    g_mqtt_connect_status = 1;
                    DEBUG_TRACE("IOT_MQTT_Construct Success");
                }
            }
        }
            

比较特别的是

  • 在异步模式下,如果IOT_MQTT_Construct()返回成功,仅表示socket连接函数调用成功,而不代表MQTT长连接已经建立
  • 调用结果是通过MSG_ID_APP_SOC_NOTIFY_IND事件返回的,由上面提到的mqtt_task_main()任务处理
void mqtt_task_main(task_entry_struct *task_entry_ptr)
  {
      ...
      while(1)
      {
          receive_msg_ext_q( task_info_g[task_entry_ptr->task_indx].task_ext_qid, &current_ilm);
          stack_set_active_module_id(my_index, current_ilm.dest_mod_id);
          switch (current_ilm.msg_id)
          {
              case MSG_ID_APP_SOC_NOTIFY_IND:
              {
                  ERROR_TRACE("%s,%d", __FUNCTION__,__LINE__);
                  ali_socket_notify(current_ilm.local_para_ptr);
                  break;
              }
            

TCP连接建立成功时,调用SDK提供的接口IOT_MQTT_Nwk_Event_Handler()建立MQTT连接

TCP建连成功时,Nucleus系统会产生SOC_CONNECT事件,我们编写了ali_socket_notify()函数包装 IOT_MQTT_Nwk_Event_Handler()

void ali_socket_notify(void *msg_ptr)
{
    ...
    switch (soc_notify->event_type)
    {
        ...
        case SOC_CONNECT:
        {
            DEBUG_TRACE("Ali SOC_CONNECT Event");
            ret = IOT_MQTT_Nwk_Event_Handler(g_mqtt_handle, IOTX_MQTT_SOC_CONNECTED  ,&nwk_param);
            if (ret == SUCCESS_RETURN) {
                g_mqtt_connect_status = 2;
                DEBUG_TRACE("Aliyun connect success,start yield");
                example_subscribe(g_mqtt_handle);
                StopTimer(ALIYUN_YIELD_TIMER);
                StartTimer(ALIYUN_YIELD_TIMER,1000*2,ali_mqtt_yield);
            }
        }
            

以上的代码除了通过IOTX_MQTT_SOC_CONNECTED事件驱动IOT_MQTT_Nwk_Event_Handler()建立MQTT连接,也订阅了Topic,并启动了定时器ali_mqtt_yield()来维持心跳

启动心跳定时器,调用SDK提供的接口IOT_MQTT_Yield()维持长连接

void ali_mqtt_yield(void)
{
    static int send_interval = 0;
    DEBUG_TRACE("ali_mqtt_yield...");
    IOT_MQTT_Yield(g_mqtt_handle,200);

    send_interval+=5;
    if (send_interval == 20) {
        send_interval = 0;
        example_publish(g_mqtt_handle);
    }
    StartTimer(ALIYUN_YIELD_TIMER,1000*5,ali_mqtt_yield);
}
            

在异步模式下,IOT_MQTT_Yield()已经变得 只发不收了,它只会起到维持心跳的作用

在心跳的定时器中,调用SDK提供的接口IOT_MQTT_Publish()发出上行的报文消息给自己

int example_publish(void *handle)
{
    int res = 0;
    iotx_mqtt_topic_info_t topic_msg;
    char product_key[IOTX_PRODUCT_KEY_LEN] = {0};
    char device_name[IOTX_DEVICE_NAME_LEN] = {0};
    const char *fmt = "/%s/%s/get";
    char topic[128] = {0};
    char *payload = "hello,world";

    HAL_GetProductKey(product_key);
    HAL_GetDeviceName(device_name);

    HAL_Snprintf(topic, 128, fmt, product_key, device_name);

    memset(&topic_msg, 0x0, sizeof(iotx_mqtt_topic_info_t));
    topic_msg.qos = IOTX_MQTT_QOS0;
    topic_msg.retain = 0;
    topic_msg.dup = 0;
    topic_msg.payload = (void *)payload;
    topic_msg.payload_len = strlen(payload);

    res = IOT_MQTT_Publish(handle, topic, &topic_msg);
    if (res < 0) {
        DEBUG_TRACE("publish failed\n");
        return -1;
    }
    return 0;
}
            

由于例程所使用的设备是特别在控制台配置过的,它的/${productKey}/${deviceName}/get可订阅也可发布,我们前面建连成功时已经订阅了它,现在又向它发送报文,因此可以看到

通过协议栈底层驱动,接收被订阅Topic上的报文

这仍然是通过包装了IOT_MQTT_Nwk_Event_Handler()ali_socket_notify()函数来处理的

void ali_socket_notify(void *msg_ptr)
{
    ...
    switch (soc_notify->event_type)
    {
        case SOC_READ:
        {
            DEBUG_TRACE("Ali SOC_READ Event");
            IOT_MQTT_Nwk_Event_Handler(g_mqtt_handle,IOTX_MQTT_SOC_READ,&nwk_param);
            DEBUG_TRACE("Ali SOC_READ End");
        }
        break;
            

当有数据可读的时候,Nucleus的协议栈会以SOC_READ事件通知,在此如果不想做其它处理,直接用IOT_MQTT_Nwk_Event_Handler()交给SDK,即可分发给当初订阅相应Topic时注册的函数处理

烧录固件

在本示例程序的开发环境中,编译固件的环境是:

打开cmd.exe命令行,通过MTK提供的编译脚本启动固件的构建过程。

然后将编译出来的固件用FlashTool工具烧录到设备上。

  • 选择config file,在 build目录下后缀.cfg结尾文件。
  • 选择option设置,第一次烧录或有异常问题时,勾选Format FAT
  • 点击download,将设备的USB口连接电脑即开始下载。

观察运行效果

打开Catcher调试工具,由于MOD_NIL标记下的日志较少,例程的日志打印到MOD_NIL 这个filter上,对其筛选可查看例程输出的日志。

用串口工具操作GPRS设备连网成功,例程则开始工作,可在Catcher中观察到它建立MQTT连接,订阅、发布和接收到报文过程中的日志。

同时若登录到IoT云端控制台(https://iot.console.aliyun.com),也可以看到对应的设备上线,并看到它发上来的报文,表明接入成功。