Usage example

Updated at:

This topic uses the ./demos/mqtt_v5_basic_demo.c file in the C Link software development kit (SDK) as an example to describe how to call the C Link SDK APIs to connect a device that uses the Message Queuing Telemetry Transport (MQTT) 5.0 protocol to IoT Platform and send and receive messages.

Background information

For more information about connecting to MQTT 5.0, see Overview.

Step 1: Initialization

  1. You can add the header files.

    #include "aiot_state_api.h"
    #include "aiot_sysdep_api.h"
    #include "aiot_mqtt_api.h"
  2. You can configure underlying dependencies and log output.

        aiot_sysdep_set_portfile(&g_aiot_sysdep_portfile);
        aiot_state_set_logcb(demo_state_logcb);
  3. You can call aiot_mqtt_init to create an ApsaraMQ for MQTT client instance and initialize the default parameters.

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

Step 2: Configure features

You can call aiot_mqtt_setopt to configure the following features. For more information about configuration items for other features, see aiot_mqtt_option_t.

  1. You can configure connection parameters.

    • Sample code:

       char *product_key = "a18wP******";
       char *device_name = "LightSwitch";
       char *device_secret = "uwMTmVAMnGGHaAkqmeDY6cHxxB******";
       char *mqtt_host = "iot-06z00ax1o******.mqtt.iothub.aliyuncs.com";
       ...
       ...
       /* Set the MQTT protocol version. */
       protocol_version = AIOT_MQTT_VERSION_5_0;
       aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_VERSION, (void *)&protocol_version);
       /* Set the MQTT server address. */
       aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_HOST, (void *)url);
       /* Set the MQTT server port. */
       aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PORT, (void *)&port);
       /* Set the device ProductKey. */
       aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_PRODUCT_KEY, (void *)product_key);
       /* Set the device DeviceName. */
       aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_NAME, (void *)device_name);
       /* Set the device DeviceSecret. */
       aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_DEVICE_SECRET, (void *)device_secret);
       /* Set the security credentials for the network connection. */
       aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_NETWORK_CRED, (void *)&cred);
       /* To use the assigned clientId feature of MQTT 5.0, set use_assigned_clientid to 1. */
       uint8_t use_assigned_clientid = 0;
       aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_ASSIGNED_CLIENTID, (void *)(&use_assigned_clientid));
    • Related parameters:

      Parameter

      Example

      Description

      mqtt_host

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

      The domain name for device connection.

      • Enterprise instances and new public instances: On the Instance Details page, view the domain name on the Development Configurations panel.

      • Legacy public instances: The domain name format is ${YourProductKey}.iot-as-mqtt.${YourRegionId}.aliyuncs.com.

      For more information about new and legacy public instances, Enterprise instances, and connection domain names, see View instance endpoints.

      product_key

      a18wP******

      Device authentication information. For more information, see Obtain device credentials.

      This example uses one-secret-per-device authentication.

      device_name

      LightSwitch

      device_secret

      uwMTmVAMnGGHaAkqmeDY6cHxxB******

      protocol_version

      AIOT_MQTT_VERSION_5_0

      Set the MQTT protocol version to 5.0.

      use_assigned_clientid

      1

      When you use the assigned clientId feature of MQTT 5.0, set use_assigned_clientid to 1.

    • Description of MQTT keepalive:

      Important
      • The device must send at least one message, such as a ping request, within each keepalive interval.

      • The heartbeat timer starts when IoT Platform sends a CONNACK message in response to a CONNECT message. The timer is reset when a PUBLISH, SUBSCRIBE, PING, or PUBACK message is received. IoT Platform checks the device's keepalive heartbeat every 30 seconds. The waiting time for the check is the duration from when the device comes online to the next scheduled check. The maximum timeout period is defined as: Keepalive heartbeat time × 1.5 + Waiting time for the check. If no message is received from the device before the maximum timeout period elapses, the server automatically disconnects the device.

      The C Link SDK has a keepalive feature. You can set the following configuration items to customize the keepalive heartbeat for the device connection. If you do not configure them, the default values are used.

      Configuration item

      Default value

      Description

      AIOT_MQTTOPT_HEARTBEAT_MAX_LOST

      2

      The threshold for tolerable heartbeat loss. After the number of heartbeat request messages reaches the specified threshold, a reconnection is initiated.

      AIOT_MQTTOPT_HEARTBEAT_INTERVAL_MS

      25,000

      The interval between reconnection attempts. Unit: milliseconds. Valid values: 1,000 to 1,200,000.

      AIOT_MQTTOPT_KEEPALIVE_SEC

      1,200

      The time threshold for tolerable heartbeat loss. After a heartbeat is lost, reconnection is allowed within the specified time. Unit: seconds. Valid values: 30 to 1,200. A value greater than 300 is recommended.

  2. You can configure status monitoring and message callbacks.

    1. You can configure the status monitoring callback function.

      • Example code:

         int main(int argc, char *argv[])
        {
         ...
         ...
        
         /* Configure the default MQTT message receiving callback function. */
         aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_RECV_HANDLER, (void *)demo_mqtt_default_recv_handler);
         /* Configure the MQTT event callback function. */
         aiot_mqtt_setopt(mqtt_handle, AIOT_MQTTOPT_EVENT_HANDLER, (void *)demo_mqtt_event_handler);
         ...
         ...
        }
         
      • Parameters:

        Configuration item

        Example value

        Description

        AIOT_MQTTOPT_RECV_HANDLER

        demo_mqtt_default_recv_handler

        When a message is received, the corresponding processing is performed based on the logic defined in this callback function.

        AIOT_MQTTOPT_EVENT_HANDLER

        demo_mqtt_event_handler

        When the device connection status changes, the corresponding processing is performed based on the logic defined in this callback function.

    2. You can define the status monitoring callback function.

      Important
      • Do not define time-consuming event processing logic because it can block the packet receiving thread.

      • Connection status changes include network exceptions, successful automatic reconnections, and disconnections.

      • If you want to handle connection status changes, you can modify the code in the TODO section as needed.

      /* This is the MQTT event callback function. It is triggered when the network is connected, reconnected, or disconnected. For event definitions, see core/aiot_mqtt_api.h. */
      void demo_mqtt_event_handler(void *handle, const aiot_mqtt_event_t *event, void *userdata)
      {
       switch (event->type) {
       /* The aiot_mqtt_connect() API is called to establish a connection with the MQTT server. */
       case AIOT_MQTTEVT_CONNECT: {
       printf("AIOT_MQTTEVT_CONNECT\n");
       /* TODO: Handle the successful SDK connection. Do not call time-consuming blocking functions here. */
       }
       break;
      
       /* The SDK is passively disconnected due to network issues and then successfully initiates an automatic reconnection. */
       case AIOT_MQTTEVT_RECONNECT: {
       printf("AIOT_MQTTEVT_RECONNECT\n");
       /* TODO: Handle the successful SDK reconnection. Do not call time-consuming blocking functions here. */
       }
       break;
      
       /* The SDK is passively disconnected due to network issues. The underlying network read/write operation failed, or no heartbeat acknowledgement was received from the server as expected. */
       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: Handle the passive SDK disconnection. Do not call time-consuming blocking functions here. */
       }
       break;
      
       default: {
      
       }
       }
      }
       
    3. You can define the message receiving callback function.

      Important
      • Do not define time-consuming message processing logic because it can block the packet receiving thread.

      • If you want to handle received messages, you can modify the code in the TODO section as needed.

      /* This is the default MQTT message processing callback. It is called when the SDK receives an MQTT message from the server and there is no corresponding user callback to process it. */
      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: Process the server's response to the heartbeat. This is generally not processed. */
              }
              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: Process the server's response to the subscription request. This is generally not processed. */
              }
              break;
              case AIOT_MQTTRECV_UNSUB_ACK: {
                  printf("unsuback, , packet id: %d\n",
                         packet->data.unsub_ack.packet_id);
                  /* TODO: Process the server's response to the subscription request. This is generally not processed. */
              }
              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: Process the server's response to a QoS 1 message. This is generally not processed. */
              }
              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: {
      
      
              }
          }
      }

Step 3: Request a connection

You can call aiot_mqtt_connect_v5 to send a connection authentication request to IoT Platform based on the configured connection parameters.

Note

For more information about the user property (MQTT_PROP_ID_USER_PROPERTY) added in the sample code, see 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);
/* Establish a connection with the server using MQTT 5.0. */
res = aiot_mqtt_connect_v5(mqtt_handle, NULL, conn_props);
aiot_mqtt_props_deinit(&conn_props);
if (res < STATE_SUCCESS) {
    /* If the connection attempt fails, destroy the MQTT instance and release resources. */
    aiot_mqtt_deinit(&mqtt_handle);
    printf("aiot_mqtt_connect failed: -0x%04X\n\r\n", -res);
    printf("please check variables like mqtt_host, product_key, device_name, device_secret in demo\r\n");
    return -1;
}

Step 4: Start the keepalive thread

You can call aiot_mqtt_process to send heartbeat messages to the server. This keeps the device in a persistent connection state and resends unacknowledged messages with a QoS=1 setting.

  1. You can start the keepalive thread.

        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. You can set the keepalive thread handler function.

    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;
    }

Step 5: Start the receiving thread

You can call aiot_mqtt_recv to receive MQTT messages from the server and process them based on the message callback function. If the device is disconnected, it automatically reconnects and performs actions based on the event callback function.

  1. You can start the receiving thread.

        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. You can set the receiving thread handler function.

    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;
    }

Step 6: Subscribe to a topic

You can call aiot_mqtt_sub_v5 to subscribe to a specified topic.

  • Sample code:

        /* This is an example of the MQTT topic subscription feature. Use it as needed for your business. */
        {
            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);
            /* Subscription options */
            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;
            }
        }
    Note
    • For more information about the user property (MQTT_PROP_ID_USER_PROPERTY) added in the sample code, see mqtt_property_identify_t.

    • For more information about subscription options, see sub_options_t.

  • Related parameters:

    Parameter

    Example

    Description

    sub_topic

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

    A topic that you have permission to subscribe to. In this topic:

    • a18wP****** is the ProductKey of the device.

    • LightSwitch is the DeviceName of the device.

    This example uses a default custom topic.

    The device can receive messages from IoT Platform through this topic.

    For more information about topics, see What is a topic?.

    sub_props

    aiot_mqtt_props_init()

    Additional subscription properties.

    opts

    .no_local = 1,

    .qos = 1,

    .retain_as_publish = 1,

    .retain_handling = 1,

    Subscription options.

Step 7: Send a message

You can call aiot_mqtt_pub_v5 to send a message to a specified topic.

  • Sample code:

    	mqtt_properties_t *pub_props = aiot_mqtt_props_init();
      /* This is an example of the MQTT message publishing feature. Use it as needed for your business. */
      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;
       }
    Note

    For more information about the user property (MQTT_PROP_ID_USER_PROPERTY) added in the sample code, see mqtt_property_identify_t.

  • Related parameters:

    Parameter

    Example

    Description

    pub_topic

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

    A topic that you have permission to publish to. In this topic:

    • a18wP****** is the ProductKey of the device.

    • LightSwitch is the DeviceName of the device.

    The device sends messages to IoT Platform through this topic.

    For more information about topics, see What is a topic?.

    pub_payload

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

    The content of the message reported to IoT Platform.

    Because the topic category for the sample message is a custom topic, the data format can be customized.

    For more information about data formats, see Data formats.

    pub_props

    See the sample code.

    Properties carried in the published message.

After the device establishes MQTT communication with IoT Platform, the communication volume must not exceed the threshold.

Step 8: Disconnect

You can call aiot_mqtt_disconnect_v5 to send a disconnection message to IoT Platform and then disconnect from the network.

For more information about the user property (MQTT_PROP_ID_USER_PROPERTY) added in the sample code, see mqtt_property_identify_t.

Note

MQTT is often used for devices with persistent connections, so the program usually does not run to this step. The main thread's task in this example is to configure parameters and establish a connection. After the connection is established, the main thread can enter hibernation.

    {
        mqtt_properties_t *disconn_props = aiot_mqtt_props_init();
        /* Reason code 0x0 indicates a 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);
    }

Step 9: Exit the program

You can call aiot_mqtt_deinit to destroy the ApsaraMQ for MQTT client instance and release resources.

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

What to do next

  • After you configure the sample file, you can compile it to generate the executable file ./output/mqtt-v5-basic-demo.

    For more information, see Compile and run.

  • For a detailed description of the run results, see the operational log.