本文以C Link SDK中的Demo文件./demos/fota_posix_demo.c为例,介绍如何调用Link SDK的API,帮助您实现子设备通过网关接入物联网平台。

背景信息

  • 网关与子设备的更多信息,请参见概述
  • 子设备的网关设备需与物联网平台保持长连接,接入方法与直连设备一致。更多信息,请参见MQTT接入概述

    本文示例中,网关设备的身份认证信息如下:

    ProductKey DeviceName DeviceSecret
    a18wP****** LightSwitchGW uwMTmVAMnGGHaAkqmeDY6cHxxB******

步骤一:初始化

  1. 添加头文件。
    ……
    ……
    
    #include "aiot_subdev_api.h"
  2. 配置底层依赖和日志输出。
        aiot_sysdep_set_portfile(&g_aiot_sysdep_portfile);
        aiot_state_set_logcb(demo_state_logcb);
  3. 调用aiot_subdev_init,创建subdev客户端实例,并初始化默认参数。
        subdev_handle = aiot_subdev_init();
        if (subdev_handle == NULL) {
            printf("aiot_subdev_init failed\n");
            demo_mqtt_stop(&mqtt_handle);
            return -1;
        }

步骤二:配置功能

调用aiot_subdev_setopt,配置以下功能。

  1. 关联MQTT连接的句柄
  2. 配置网关与子设备功能的消息回调
  1. 关联MQTT连接的句柄。
    注意 在配置网关与子设备功能参数前,请确保已配置网关的设备认证信息,具体操作,请参见MQTT配置连接参数
    • 示例代码:
          aiot_subdev_setopt(subdev_handle, AIOT_SUBDEVOPT_MQTT_HANDLE, mqtt_handle);
    • 相关参数:
      配置项 示例 说明
      AIOT_SUBDEVOPT_MQTT_HANDLE mqtt_handle 网关与子设备功能的请求基于MQTT连接,通过该配置项,关联MQTT连接句柄。
  2. 配置网关与子设备功能的消息回调。
    1. 配置消息回调函数。
      • 示例代码:
            aiot_subdev_setopt(subdev_handle, AIOT_SUBDEVOPT_RECV_HANDLER, demo_subdev_recv_handler);
      • 相关参数:
        配置项 示例值 说明
        AIOT_SUBDEVOPT_RECV_HANDLER demo_subdev_recv_handler 当设备收到来自物联网平台的网关与子设备的相关消息时,触发该回调函数,根据其设置,执行对应的处理。
    2. 定义消息回调函数。
      关于消息的Alink数据格式,请参见管理拓扑关系子设备上下线
      void demo_subdev_recv_handler(void *handle, const aiot_subdev_recv_t *packet, void *user_data)
      {
          switch (packet->type) {
              case AIOT_SUBDEVRECV_TOPO_ADD_REPLY:
              case AIOT_SUBDEVRECV_TOPO_DELETE_REPLY:
              case AIOT_SUBDEVRECV_TOPO_GET_REPLY:
              case AIOT_SUBDEVRECV_BATCH_LOGIN_REPLY:
              case AIOT_SUBDEVRECV_BATCH_LOGOUT_REPLY:
              case AIOT_SUBDEVRECV_SUB_REGISTER_REPLY:
              case AIOT_SUBDEVRECV_PRODUCT_REGISTER_REPLY: {
                  printf("msgid        : %d\n", packet->data.generic_reply.msg_id);
                  printf("code         : %d\n", packet->data.generic_reply.code);
                  printf("product key  : %s\n", packet->data.generic_reply.product_key);
                  printf("device name  : %s\n", packet->data.generic_reply.device_name);
                  printf("message      : %s\n", (packet->data.generic_reply.message == NULL)?("NULL"):(packet->data.generic_reply.message));
                  printf("data         : %s\n", packet->data.generic_reply.data);
              }
              break;
              case AIOT_SUBDEVRECV_TOPO_CHANGE_NOTIFY: {
                  printf("msgid        : %d\n", packet->data.generic_notify.msg_id);
                  printf("product key  : %s\n", packet->data.generic_notify.product_key);
                  printf("device name  : %s\n", packet->data.generic_notify.device_name);
                  printf("params       : %s\n", packet->data.generic_notify.params);
              }
              break;
              default: {
      
              }
          }
      }

步骤三:添加拓扑关系

  1. 获取子设备的认证信息。
    为子设备创建对应的产品和设备,创建产品时,节点类型选择为网关子设备例如,创建子设备对应产品,并添加4个子设备。
    产品名称 ProductKey DeviceName DeviceSecret ProductSecret
    LightSwitchSD a13FN****** LightSwitch_SubDev_01 768XBgQwgOakz3K4uhOiLeeh9x****** y7GSILD480******
    LightSwitch_SubDev_02 iwTZrbjbgNVChfuJkihjE5asek******
    LightSwitch_SubDev_03 fdutq35iKMYdcWWBuIINY26hsN******
    LightSwitch_SubDev_04 HCKv50YqgwdKhy5cE0Vz4aydmK******

    具体操作,请参见创建产品创建设备

  2. 定义子设备认证信息的变量g_subdev
    示例代码为预置4个子设备的认证信息,在实际业务中,需自行编写代码,定义获取子设备认证信息的方式。例如:
    • 在网关与子设备之间定义协议,实现网关发现子设备,获取子设备的设备证书。该协议由网关厂商与子设备厂商自行定义。
    • 网关厂商可以在网关上提供某种配置方式,预置子设备的证书信息。该功能由网关厂商自行实现。
    aiot_subdev_dev_t g_subdev[] = {
        {
            "a13FN******",
            "LightSwitch_SubDev_01",
            "768XBgQwgOakz3K4uhOiLeeh9x******",
            "y7GSILD480******"
        },
        {
            "a13FN******",
            "LightSwitch_SubDev_02",
            "iwTZrbjbgNVChfuJkihjE5asek******",
            "y7GSILD480******"
        },
        {
            "a13FN******",
            "LightSwitch_SubDev_03",
            "fdutq35iKMYdcWWBuIINY26hsN******",
            "y7GSILD480******"
        },
        {
            "a13FN******",
            "LightSwitch_SubDev_04",
            "HCKv50YqgwdKhy5cE0Vz4aydmK******",
            "y7GSILD480******"
        }
    };
  3. 调用aiot_subdev_send_topo_add,向物联网平台,发送添加子设备与网关设备的拓扑关系请求。
        res = aiot_subdev_send_topo_add(subdev_handle, g_subdev, sizeof(g_subdev)/sizeof(aiot_subdev_dev_t));
        if (res < STATE_SUCCESS) {
            printf("aiot_subdev_send_topo_add failed, res: -0x%04X\n", -res);
            aiot_subdev_deinit(&subdev_handle);
            demo_mqtt_stop(&mqtt_handle);
            return -1;
        }
  4. 可选:当网关设备不再代理子设备接收物联网平台的消息时,您可以调用aiot_subdev_send_topo_delete,删除子设备与网关设备的拓扑关系。
         aiot_subdev_send_topo_delete(subdev_handle, g_subdev, sizeof(g_subdev)/sizeof(aiot_subdev_dev_t));
         if (res < STATE_SUCCESS) {
             printf("aiot_subdev_send_topo_delete failed, res: -0x%04X\n", -res);
             aiot_subdev_deinit(&subdev_handle);
             demo_mqtt_stop(&mqtt_handle);
             return -1;
         }

步骤四:登录子设备

  1. 调用aiot_subdev_send_batch_login,向物联网平台发送子设备批量登录的请求,通过建立的拓扑关系,执行登录操作后,子设备将变更为在线状态。
        aiot_subdev_send_batch_login(subdev_handle, g_subdev, sizeof(g_subdev)/sizeof(aiot_subdev_dev_t));
        if (res < STATE_SUCCESS) {
            printf("aiot_subdev_send_batch_login failed, res: -0x%04X\n", -res);
            aiot_subdev_deinit(&subdev_handle);
            demo_mqtt_stop(&mqtt_handle);
            return -1;
        }
  2. 可选:如果需要主动下线设备,您可以调用aiot_subdev_send_batch_logout,向物联网平台发送断开连接的请求,物联网平台接收请求消息后,执行下线操作,子设备将变更为离线状态。
    注意 通过该接口,物联网平台更新子设备状态为离线,避免网关收到发送给子设备的消息。
         aiot_subdev_send_batch_logout(subdev_handle, g_subdev, sizeof(g_subdev)/sizeof(aiot_subdev_dev_t));
         if (res < STATE_SUCCESS) {
             printf("aiot_subdev_send_batch_logout failed, res: -0x%04X\n", -res);
             aiot_subdev_deinit(&subdev_handle);
             demo_mqtt_stop(&mqtt_handle);
             return -1;
         }
子设备登录或登出后,物联网平台对发送子设备的消息,根据其对应状态,执行以下操作:
  • 如果子设备离线,物联网平台发送给子设备的QoS=0消息立即丢弃。
  • 如果子设备在线,物联网平台将子设备的消息发送给对应网关设备,然后由网关设备将子设备的消息转发给子设备。

步骤五:子设备订阅Topic

子设备通过网关设备接入物联网平台后,可以调用aiot_mqtt_sub,订阅子设备的Topic,接收对应Topic的消息。

注意 订阅Topic时,注意区分子设备和网关设备的ProductKeyDeviceName,确保订阅所需设备的Topic。
  • 示例代码:
        {
            char *sub_topic = "/a13FN******/LightSwitch_SubDev_01/user/get";
    
            res = aiot_mqtt_sub(mqtt_handle, sub_topic, NULL, 1, NULL);
            if (res < 0) {
                printf("aiot_mqtt_sub failed, res: -0x%04X\n", -res);
                return -1;
            }
        }
  • 相关参数:
    参数 示例 说明
    sub_topic /a13FN******/LightSwitch_SubDev_01/user/get
    拥有订阅权限的Topic。其中,
    • a13FN******为子设备的ProductKey。
    • LightSwitch_SubDev_01为子设备的DeviceName。

    本示例为子设备默认的自定义Topic,设备通过该Topic,可接收物联网平台的消息。

    关于Topic的更多信息,请参见什么是Topic

步骤六:子设备发布消息

调用aiot_mqtt_pub,向子设备的指定Topic发送消息。

  • 示例代码:
         {
            char *pub_topic = "/a13FN******/LightSwitch_SubDev_01/user/update";
            char *pub_payload = "{\"id\":\"1\",\"version\":\"1.0\",\"params\":{\"LightSwitch\":0}}";
    
            res = aiot_mqtt_pub(mqtt_handle, pub_topic, (uint8_t *)pub_payload, (uint32_t)strlen(pub_payload), 0);
            if (res < 0) {
                printf("aiot_mqtt_sub failed, res: -0x%04X\n", -res);
                return -1;
            }
        }
  • 相关参数:
    参数 示例 说明
    pub_topic /a13FN******/LightSwitch_SubDev_01/user/update 拥有发布权限的Topic。其中,
    • a13FN******为子设备的ProductKey。
    • LightSwitch_SubDev_01为子设备的DeviceName。
    本示例为子设备默认的自定义Topic,设备通过该Topic向物联网平台发送消息。

    关于Topic的更多信息,请参见什么是Topic

    pub_payload {\"id\":\"1\",\"version\":\"1.0\",\"params\":{\"LightSwitch\":0}} 上报至物联网平台的消息内容。

    由于示例消息的Topic类型为自定义,因此数据格式可自定义。

    关于数据格式的更多信息,请参见数据格式

步骤七:断开网关连接

说明

MQTT接入常应用于长连接的设备,程序通常不会运行至此。

例程的主线程任务为配置参数并成功建立连接。连接建立后,主线程可进入休眠。

调用aiot_mqtt_disconnect,向物联网平台发送断开连接的报文,然后断开网络连接。

    res = aiot_mqtt_disconnect(mqtt_handle);
    if (res < STATE_SUCCESS) {
        aiot_mqtt_deinit(&mqtt_handle);
        printf("aiot_mqtt_disconnect failed: -0x%04X\n", -res);
        return -1;
    }

步骤八:退出程序

调用aiot_subdev_deinit,销毁subdev客户端实例,释放资源。

    res = aiot_subdev_deinit(&subdev_handle);
    if (res < STATE_SUCCESS) {
        printf("aiot_subdev_deinit failed: -0x%04X\n", res);
    }

后续步骤

  • 例程文件配置完成后,需进行编译,生成可执行文件./output/subdev-basic-demo

    更多信息,请参见编译与运行

  • 关于运行结果的详细说明,请参见运行日志