全部產品
Search
文件中心

:使用樣本

更新時間:Jun 30, 2024

本文以C Link SDK中的Demo檔案./demos/mqtt_v5_basic_demo.c為例,介紹如何調用C Link SDK的API,將MQTT 5.0協議的裝置接入物聯網平台並進行訊息收發。

背景資訊

MQTT 5.0接入的更多資訊,請參見概述

步驟一:初始化

  1. 添加標頭檔。

    #include "aiot_state_api.h"
    #include "aiot_sysdep_api.h"
    #include "aiot_mqtt_api.h"
  2. 配置底層依賴和日誌輸出。

        aiot_sysdep_set_portfile(&g_aiot_sysdep_portfile);
        aiot_state_set_logcb(demo_state_logcb);
  3. 調用aiot_mqtt_init,建立MQTT用戶端執行個體,並初始化預設參數。

        mqtt_handle = aiot_mqtt_init();
        if (mqtt_handle == NULL) {
            printf("aiot_mqtt_init failed\n");
            return -1;
        }

步驟二:配置功能

調用aiot_mqtt_setopt,配置以下功能。更多功能的配置項,請參見aiot_mqtt_option_t

  1. 配置串連參數。

    • 範例程式碼:

       char *product_key = "a18wP******";
       char *device_name = "LightSwitch";
       char *device_secret = "uwMTmVAMnGGHaAkqmeDY6cHxxB******";
       char *mqtt_host = "iot-06z00ax1o******.mqtt.iothub.aliyuncs.com";
       ...
       ...
       /* 配置MQTT協議的版本 */
       protocol_version = AIOT_MQTT_VERSION_5_0;
       aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_VERSION, (void *)&protocol_version);
       /* 配置MQTT伺服器位址。 */
       aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_HOST, (void *)url);
       /* 配置MQTT伺服器連接埠。 */
       aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PORT, (void *)&port);
       /* 配置裝置ProductKey。 */
       aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PRODUCT_KEY, (void *)product_key);
       /* 配置裝置DeviceName。 */
       aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_NAME, (void *)device_name);
       /* 配置裝置DeviceSecret。 */
       aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_SECRET, (void *)device_secret);
       /* 配置網路連接的安全憑據。 */
       aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_NETWORK_CRED, (void *)&cred);
       /* 如果要使用MQTT 5.0的assigned clientId功能, 需要將use_assigned_clientid置為1 */
       uint8_t use_assigned_clientid = 0;
       aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_ASSIGNED_CLIENTID, (void *)(&use_assigned_clientid));
    • 相關參數:

      參數

      樣本

      說明

      mqtt_host

      iot-06z00ax1o******.mqtt.iothub.aliyuncs.com

      裝置接入網域名稱。

      • 企業版執行個體和新版公用執行個體:在執行個體詳情頁面的開發配置面板,查看接入網域名稱。

      • 舊版公用執行個體:接入網域名稱格式為${YourProductKey}.iot-as-mqtt.${YourRegionId}.aliyuncs.com

      新舊版公用執行個體和企業版執行個體、以及接入網域名稱的更多資訊,請參見查看執行個體終端節點

      product_key

      a18wP******

      裝置認證資訊。更多資訊,請參見擷取裝置認證資訊

      本常式的身份認證方式為一機一密。

      device_name

      LightSwitch

      device_secret

      uwMTmVAMnGGHaAkqmeDY6cHxxB******

      protocol_version

      AIOT_MQTT_VERSION_5_0

      配置MQTT協議的版本為5.0。

      use_assigned_clientid

      1

      使用MQTT 5.0的assigned clientId功能時,需要將use_assigned_clientid置為1。

    • MQTT保活說明:

      重要
      • 裝置端在保活時間間隔內,至少需要發送一次報文,包括ping請求。

      • 從物聯網平台發送CONNACK響應CONNECT訊息時,開始心跳計時。收到PUBLISHSUBSCRIBEPINGPUBACK訊息時,會重設計時器。物聯網平台每隔30秒定時檢測一次裝置的保活心跳,裝置上線時間點距離最新定時檢測時間點的時間,是定時檢測的等待時間。定義最大逾時時間為:保活心跳時間*1.5+定時檢測的等待時間。超過最大逾時時間未收到裝置訊息,伺服器會自動中斷連線。

      C Link SDK具備保活能力,您可以設定以下配置項,自訂裝置串連的保活心跳。如果不配置,則取預設值。

      配置項

      預設值

      說明

      AIOT_MQTTOPT_HEARTBEAT_MAX_LOST

      2

      可容忍的心跳丟失閾值。即:心跳請求報文達到設定的次數後,發起重連。

      AIOT_MQTTOPT_HEARTBEAT_INTERVAL_MS

      25,000

      每次發起重連之間的間隔時間。單位毫秒, 取值範圍:1,000~1,200,000。

      AIOT_MQTTOPT_KEEPALIVE_SEC

      1,200

      可容忍的心跳丟失時間閾值。即:失去心跳後,設定的時間內,允許發起重連。單位秒,取值範圍:30~1,200。建議取值大於300。

  2. 配置狀態監控和訊息回調。

    1. 配置狀態監控回呼函數。

      • 範例程式碼:

         int main(int argc, char *argv[])
        {
         ...
         ...
        
         /* 配置MQTT預設訊息接收回呼函數。 */
         aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_RECV_HANDLER, (void *)demo_mqtt_default_recv_handler);
         /* 配置MQTT事件回呼函數。 */
         aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_EVENT_HANDLER, (void *)demo_mqtt_event_handler);
         ...
         ...
        }
         
      • 相關參數:

        配置項

        樣本值

        說明

        AIOT_MQTTOPT_RECV_HANDLER

        demo_mqtt_default_recv_handler

        當接收訊息時,根據該回呼函數定義的處理邏輯,執行對應的處理。

        AIOT_MQTTOPT_EVENT_HANDLER

        demo_mqtt_event_handler

        當裝置串連狀態發生變化時,根據該回呼函數定義的處理邏輯,執行對應的處理。

    2. 定義狀態監控的回呼函數。

      重要
      • 請勿將事件的處理邏輯,定義得過於耗時,以免阻塞收包線程。

      • 串連狀態的變化包括網路異常、自動重連已成功、已中斷連線等。

      • 如果要根據串連狀態的變化做應對處理,可在TODO處,按照需要修改代碼。

      /* MQTT事件回呼函數, 當網路連接、重連或斷開時,觸發該函數, 事件定義見core/aiot_mqtt_api.h。 */
      void demo_mqtt_event_handler(void *handle, const aiot_mqtt_event_t *event, void *userdata)
      {
       switch (event->type) {
       /* 調用了aiot_mqtt_connect()介面, 與MQTT伺服器建立串連。 */
       case AIOT_MQTTEVT_CONNECT: {
       printf("AIOT_MQTTEVT_CONNECT\n");
       /* TODO: 處理SDK建立串連成功, 不可在此調用耗時較長的阻塞函數。 */
       }
       break;
      
       /* SDK因網路狀況被動中斷連線後, 成功自動發起重連。 */
       case AIOT_MQTTEVT_RECONNECT: {
       printf("AIOT_MQTTEVT_RECONNECT\n");
       /* TODO: 處理SDK重連成功, 不可在此調用耗時較長的阻塞函數。 */
       }
       break;
      
       /* SDK因網路狀況被動斷開了串連, network底層讀寫失敗, heartbeat沒有按預期得到服務端心跳應答。 */
       case AIOT_MQTTEVT_DISCONNECT: {
       char *cause = (event->data.disconnect == AIOT_MQTTDISCONNEVT_NETWORK_DISCONNECT) ? ("network disconnect") :
       ("heartbeat disconnect");
       printf("AIOT_MQTTEVT_DISCONNECT: %s\n", cause);
       /* TODO: 處理SDK被動中斷連線, 不可在此調用耗時較長的阻塞函數。 */
       }
       break;
      
       default: {
      
       }
       }
      }
       
    3. 定義訊息接收的回呼函數。

      重要
      • 請勿將訊息的處理邏輯,定義得過於耗時,以免阻塞收包線程。

      • 如果您要根據接收的訊息做應對處理,可在TODO處,按照需要修改代碼。

      /* MQTT預設訊息處理回調, 當SDK從伺服器收到MQTT訊息時, 且無對應使用者回調處理時被調用 */
      void demo_mqtt_default_recv_handler(void *handle, const aiot_mqtt_recv_t *packet, void *userdata)
      {
          switch (packet->type) {
              case AIOT_MQTTRECV_HEARTBEAT_RESPONSE: {
                  printf("heartbeat response\n");
                  /* TODO: 處理伺服器對心跳的回應, 一般不處理 */
              }
              break;
      
      
              case AIOT_MQTTRECV_SUB_ACK: {
                  printf("suback, res: -0x%04X, packet id: %d, max qos: %d\n",
                         -packet->data.sub_ack.res, packet->data.sub_ack.packet_id, packet->data.sub_ack.max_qos);
                  /* TODO: 處理伺服器對訂閱請求的回應, 一般不處理 */
              }
              break;
              case AIOT_MQTTRECV_UNSUB_ACK: {
                  printf("unsuback, , packet id: %d\n",
                         packet->data.unsub_ack.packet_id);
                  /* TODO: 處理伺服器對訂閱請求的回應, 一般不處理 */
              }
              break;
              case AIOT_MQTTRECV_PUB: {
                  printf("pub, qos: %d, topic: %.*s\n", packet->data.pub.qos, packet->data.pub.topic_len, packet->data.pub.topic);
                  printf("pub, payload: %.*s\n", packet->data.pub.payload_len, packet->data.pub.payload);
                  printf("pub, payload len: %x\n", packet->data.pub.payload_len);
                  aiot_mqtt_props_print(packet->data.pub.props);
              }
              break;
      
      
              case AIOT_MQTTRECV_PUB_ACK: {
                  printf("puback, packet id: %d\n", packet->data.pub_ack.packet_id);
                  /* TODO: 處理伺服器對QoS1上報訊息的回應, 一般不處理 */
              }
              break;
      
              case AIOT_MQTTRECV_CON_ACK: {
                  aiot_mqtt_props_print(packet->data.con_ack.props);
              }
              break;
              case AIOT_MQTTRECV_DISCONNECT: {
                  printf("server disconnect, reason code: 0x%x\n", packet->data.server_disconnect.reason_code);
              }
              break;
       
              default: {
      
      
              }
          }
      }

步驟三:請求串連

調用aiot_mqtt_connect_v5,根據配置串連的參數,向物聯網平台,發起串連認證請求。

說明

範例程式碼中添加的使用者屬性(MQTT_PROP_ID_USER_PROPERTY)更多資訊,請參見mqtt_property_identify_t

mqtt_properties_t *conn_props = aiot_mqtt_props_init();
mqtt_property_t user_prop = {
    .id = MQTT_PROP_ID_USER_PROPERTY,
    .value.str_pair.key.len = strlen("demo_key"),
    .value.str_pair.key.value = (uint8_t *)"demo_key",
    .value.str_pair.value.len = strlen("demo_value"),
    .value.str_pair.value.value = (uint8_t *)"demo_value",
};
aiot_mqtt_props_add(conn_props, &user_prop);
/* 通過MQTT 5.0的方式與伺服器建連 */
res = aiot_mqtt_connect_v5(mqtt_handle, NULL, conn_props);
aiot_mqtt_props_deinit(&conn_props);
if (res < STATE_SUCCESS) {
    /* 嘗試建立串連失敗, 銷毀MQTT執行個體, 回收資源 */
    aiot_mqtt_deinit(&mqtt_handle);
    printf("aiot_mqtt_connect failed: -0x%04X\n\r\n", -res);
    printf("please check variables like mqtt_host, produt_key, device_name, device_secret in demo\r\n");
    return -1;
}

步驟四:開啟保活線程

調用aiot_mqtt_process,向伺服器發送心跳報文,使裝置保持長串連狀態,並重發QoS=1的未應答報文。

  1. 開啟保活線程。

        res = pthread_create(&g_mqtt_process_thread, NULL, demo_mqtt_process_thread, mqtt_handle);
        if (res < 0) {
            printf("pthread_create demo_mqtt_process_thread failed: %d\n", res);
            return -1;
        }
  2. 設定保活線程處理函數。

    void *demo_mqtt_process_thread(void *args)
    {
        int32_t res = STATE_SUCCESS;
    
        while (g_mqtt_process_thread_running) {
            res = aiot_mqtt_process(args);
            if (res == STATE_USER_INPUT_EXEC_DISABLED) {
                break;
            }
            sleep(1);
        }
        return NULL;
    }

步驟五:開啟接收線程

調用aiot_mqtt_recv,收取伺服器下發的MQTT訊息,根據訊息回呼函數,執行對應處理。在斷線時自動重連,根據事件回呼函數,執行對應處理。

  1. 開啟接收線程。

        res = pthread_create(&g_mqtt_recv_thread, NULL, demo_mqtt_recv_thread, mqtt_handle);
        if (res < 0) {
            printf("pthread_create demo_mqtt_recv_thread failed: %d\n", res);
            return -1;
        }
                                        
  2. 設定接收線程處理函數。

    void *demo_mqtt_recv_thread(void *args)
    {
        int32_t res = STATE_SUCCESS;
    
        while (g_mqtt_recv_thread_running) {
            res = aiot_mqtt_recv(args);
            if (res < STATE_SUCCESS) {
                if (res == STATE_USER_INPUT_EXEC_DISABLED) {
                    break;
                }
                sleep(1);
            }
        }
        return NULL;
    }

步驟六:訂閱Topic

調用aiot_mqtt_sub_v5,訂閱指定Topic。

  • 範例程式碼:

        /* MQTT 訂閱topic功能樣本, 請根據自己的業務需求進行使用 */
        {
            char *sub_topic = "/sys/${YourProductKey}/${YourDeviceName}/thing/event/property/post_reply";
            mqtt_properties_t *sub_props = aiot_mqtt_props_init();
            aiot_mqtt_props_add(sub_props, &user_prop);
            /* 訂閱選項 */
            sub_options_t opts = {
                .no_local = 1,
                .qos = 1,
                .retain_as_publish = 1,
                .retain_handling = 1,
            };
            res = aiot_mqtt_sub_v5(mqtt_handle, sub_topic, &opts, NULL, NULL, sub_props);
            aiot_mqtt_props_deinit(&sub_props);
            if (res < 0) {
                printf("aiot_mqtt_sub failed, res: -0x%04X\n", -res);
                aiot_mqtt_deinit(&mqtt_handle);
                return -1;
            }
        }
    說明
    • 範例程式碼中添加的使用者屬性(MQTT_PROP_ID_USER_PROPERTY)更多資訊,請參見mqtt_property_identify_t

    • 添加訂閱選項的詳細內容,請參見sub_options_t

  • 相關參數:

    參數

    樣本

    說明

    sub_topic

    /a18wP******/LightSwitch/user/get

    擁有訂閱許可權的Topic。其中:

    • a18wP******為裝置的ProductKey。

    • LightSwitch為裝置的DeviceName。

    本樣本為預設的自訂Topic。

    裝置通過該Topic,可接收物聯網平台的訊息。

    關於Topic的更多資訊,請參見什麼是Topic

    sub_props

    aiot_mqtt_props_init()

    訂閱附加屬性。

    opts

    .no_local = 1,

    .qos = 1,

    .retain_as_publish = 1,

    .retain_handling = 1,

    訂閱選項。

步驟七:發送訊息

調用aiot_mqtt_pub_v5,向指定Topic發送訊息。

  • 範例程式碼:

    	mqtt_properties_t *pub_props = aiot_mqtt_props_init();
      /* MQTT 發布訊息功能樣本, 請根據自己的業務需求進行使用 */
      char *pub_topic = "/sys/${YourProductKey}/${YourDeviceName}/thing/event/property/post";
      char *pub_payload = "{\"id\":\"1\",\"version\":\"1.0\",\"params\":{\"LightSwitch\":0}}";
      mqtt_property_t response_prop = {
          .id = MQTT_PROP_ID_RESPONSE_TOPIC,
          .value.str.len = strlen(pub_topic),
          .value.str.value = (uint8_t *)pub_topic,
       };
       char *demo_data_str = "12345";
       mqtt_property_t correlation_prop = {
           .id = MQTT_PROP_ID_CORRELATION_DATA,
           .value.str.len = strlen(demo_data_str),
           .value.str.value = (uint8_t *)demo_data_str,
       };
       aiot_mqtt_props_add(pub_props, &response_prop);
       aiot_mqtt_props_add(pub_props, &correlation_prop);
       res = aiot_mqtt_pub_v5(mqtt_handle, pub_topic, (uint8_t *)pub_payload, (uint32_t)(strlen(pub_payload)), 1,0, pub_props);
       if (res < 0) {
           printf("aiot_mqtt pub failed, res: -0x%04X\n", -res);
           aiot_mqtt_deinit(&mqtt_handle);
           return -1;
       }
    說明

    範例程式碼中添加的使用者屬性(MQTT_PROP_ID_USER_PROPERTY)更多資訊,請參見mqtt_property_identify_t

  • 相關參數:

    參數

    樣本

    說明

    pub_topic

    /a18wP******/LightSwitch/user/update

    擁有發布許可權的Topic。其中:

    • a18wP******為裝置的ProductKey。

    • LightSwitch為裝置的DeviceName。

    裝置通過該Topic向物聯網平台發送訊息。

    關於Topic的更多資訊,請參見什麼是Topic

    pub_payload

    {\"id\":\"1\",\"version\":\"1.0\",\"params\":{\"LightSwitch\":0}}

    上報至物聯網平台的訊息內容。

    由於樣本訊息的Topic類型為自訂,因此資料格式可自訂。

    關於資料格式的更多資訊,請參見資料格式

    pub_props

    參考範例程式碼。

    發布訊息攜帶的屬性。

裝置與物聯網平台建立MQTT通訊後,請確保通訊量不超過閾值。

步驟八:中斷連線

調用aiot_mqtt_disconnect_v5,向物聯網平台發送中斷連線的報文,然後斷開網路連接。

範例程式碼中添加的使用者屬性(MQTT_PROP_ID_USER_PROPERTY)更多資訊,請參見mqtt_property_identify_t

說明

MQTT接入常應用於長串連的裝置,程式通常不會運行至此。常式的主線程任務為配置參數並成功建立串連。串連建立後,主線程可進入休眠。

    {
        mqtt_properties_t *disconn_props = aiot_mqtt_props_init();
        /* reason code 0x0 表示Normal disconnection */
        int demo_reason_code = 0x0;
        char *demo_reason_string = "normal_exit";
        mqtt_property_t reason_prop = {.id = MQTT_PROP_ID_REASON_STRING, .value.str.len = strlen(demo_reason_string), .value.str.value = (uint8_t *)demo_reason_string};
        aiot_mqtt_props_add(disconn_props, &reason_prop);
        res = aiot_mqtt_disconnect_v5(mqtt_handle, demo_reason_code, disconn_props);
        aiot_mqtt_props_deinit(&disconn_props);
    }

步驟九:退出程式

調用aiot_mqtt_deinit,銷毀MQTT用戶端執行個體,釋放資源。

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

後續步驟

  • 常式檔案配置完成後,需進行編譯,產生可執行檔./output/mqtt-v5-basic-demo

    更多資訊,請參見編譯與運行

  • 關於運行結果的詳細說明,請參見作業記錄