Check if a device is online from the server-side

Updated at:
Copy as MD

This topic describes the principle, process, and implementation of using revert-RPC (RRPC) to determine whether a device is online.

Background information

Devices connected using MQTT use heartbeats for keepalive. However, the characteristics of heartbeats, such as their periodic nature, automatic packet exchange, and reconnection upon timeout, make it difficult to proactively determine a device's online status. Although the server-side provides the GetDeviceStatus and BatchGetDeviceState APIs to query device status, these API calls are session-based, and session keepalive also relies on heartbeats.

Principle

If a device can receive a message from the server and send a response, its communication is normal, which indicates that the device is online.

Sending and receiving messages is a core capability of IoT Platform. Therefore, this determination method is not affected by IoT Platform architecture upgrades or service changes. It is also not affected by the client that the device uses. This makes it the most universal principle for checking a device's online status from the server-side.

The RRPC feature provided by IoT Platform is a special implementation of this principle.

Process

  1. The client subscribes to the RRPC request topic: /sys/${yourProductKey}/${yourDeviceName}/rrpc/request/+.

  2. The server calls the RRpc API operation to send an instruction, such as {"id":123,"version":"1.0","time":1234567890123}.

    You can customize the message content. We recommend that you use this format.

    The following table describes the parameters.

    Field

    Type

    Description

    id

    Object

    The message ID. It is used to verify that the sent and received messages are the same. Generate this ID at the service layer and ensure its uniqueness.

    version

    String

    The version number. The current version is fixed at 1.0.

    time

    Long

    The timestamp when the message is sent. You can use it to calculate the round-trip latency and evaluate the current communication quality.

  3. The client receives the instruction and responds to the RRPC request. The response message has the following format: {"id":123,"version":"1.0","time":1234567890123}

    The server uses the following logic to determine if a device is offline:

    • Strict: If a response is not received within 5 seconds after a message is sent, the attempt fails. A single failure indicates that the device is offline.

    • Normal: If a response is not received within 5 seconds after a message is sent, the attempt fails. Two consecutive failures indicate that the device is offline.

    • Loose: If a response is not received within 5 seconds after a message is sent, the attempt fails. Three consecutive failures indicate that the device is offline.

    Note

    You can customize the logic for determining the offline status based on your requirements.

Implementation

This example is developed based on the client Java SDK Demo and the server-side Java SDK Demo.

Note

You can select different client SDKs and server-side SDKs for development based on your requirements. For more information about the parameters of the RRpc API operation, see RRpc.

Download the demo projects. Add the CheckDeviceStatusOnServer class to the server-side project and the Device class to the client project. Then, enter your Alibaba Cloud account AccessKey and device certificate information.

The client code is as follows:

import java.io.UnsupportedEncodingException;

import com.aliyun.alink.dm.api.DeviceInfo;
import com.aliyun.alink.dm.api.InitResult;
import com.aliyun.alink.linkkit.api.ILinkKitConnectListener;
import com.aliyun.alink.linkkit.api.IoTMqttClientConfig;
import com.aliyun.alink.linkkit.api.LinkKit;
import com.aliyun.alink.linkkit.api.LinkKitInitParams;
import com.aliyun.alink.linksdk.cmp.connect.channel.MqttPublishRequest;
import com.aliyun.alink.linksdk.cmp.connect.channel.MqttSubscribeRequest;
import com.aliyun.alink.linksdk.cmp.core.base.AMessage;
import com.aliyun.alink.linksdk.cmp.core.base.ARequest;
import com.aliyun.alink.linksdk.cmp.core.base.AResponse;
import com.aliyun.alink.linksdk.cmp.core.base.ConnectState;
import com.aliyun.alink.linksdk.cmp.core.listener.IConnectNotifyListener;
import com.aliyun.alink.linksdk.cmp.core.listener.IConnectSendListener;
import com.aliyun.alink.linksdk.cmp.core.listener.IConnectSubscribeListener;
import com.aliyun.alink.linksdk.tools.AError;

public class Device {

    // ===================Parameters that you must configure - Start===========================
    // The ProductKey of the product. This is one of the device certificate parameters.
    private static String productKey = "";
    // The DeviceName of the device. This is one of the device certificate parameters.
    private static String deviceName = "";
    // The DeviceSecret of the device. This is one of the device certificate parameters.
    private static String deviceSecret = "";
    // The topic for message communication. You do not need to create or define it. You can use it directly.
    private static String rrpcTopic = "/sys/" + productKey + "/" + deviceName + "/rrpc/request/+";
    // ===================Parameters that you must configure - End===========================

    public static void main(String[] args) throws InterruptedException {

        Device device = new Device();

        // Initialize
        device.init(productKey, deviceName, deviceSecret);

        // Listen for downstream data
        device.registerNotifyListener();

        // Subscribe to the topic
        device.subscribe(rrpcTopic);
    }

    /**
     * Initialization
     * 
     * @param pk productKey
     * @param dn deviceName
     * @param ds deviceSecret
     * @throws InterruptedException
     */
    public void init(String pk, String dn, String ds) throws InterruptedException {

        LinkKitInitParams params = new LinkKitInitParams();

        // Set MQTT initialization parameters
        IoTMqttClientConfig config = new IoTMqttClientConfig();
        config.productKey = pk;
        config.deviceName = dn;
        config.deviceSecret = ds;
        params.mqttClientConfig = config;

        // Set the device certificate information for initialization, which is passed in by the user
        DeviceInfo deviceInfo = new DeviceInfo();
        deviceInfo.productKey = pk;
        deviceInfo.deviceName = dn;
        deviceInfo.deviceSecret = ds;

        params.deviceInfo = deviceInfo;

        LinkKit.getInstance().init(params, new ILinkKitConnectListener() {
            public void onError(AError aError) {
                System.out.println("init failed !! code=" + aError.getCode() + ",msg=" + aError.getMsg() + ",subCode="
                        + aError.getSubCode() + ",subMsg=" + aError.getSubMsg());
            }

            public void onInitDone(InitResult initResult) {
                System.out.println("init success !!");
            }
        });

        // Make sure that the following steps are executed only after successful initialization. You can extend the delay here as needed.
        Thread.sleep(2000);
    }

    /**
     * Listen for downstream data
     */
    public void registerNotifyListener() {
        LinkKit.getInstance().registerOnNotifyListener(new IConnectNotifyListener() {
            @Override
            public boolean shouldHandle(String connectId, String topic) {
                // Process messages only from a specific topic.
                if (topic.contains("/rrpc/request/")) {
                    return true;
                } else {
                    return false;
                }
            }

            @Override
            public void onNotify(String connectId, String topic, AMessage aMessage) {
                // Receive the RRPC request and reply with an RRPC response.
                try {
                    String response = topic.replace("/request/", "/response/");
                    publish(response, new String((byte[]) aMessage.getData(), "UTF-8"));
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void onConnectStateChange(String connectId, ConnectState connectState) {
            }
        });
    }

    /**
     * Publish a message
     * 
     * @param topic The topic to which the message is sent
     * @param payload The content of the message to be sent
     */
    public void publish(String topic, String payload) {
        MqttPublishRequest request = new MqttPublishRequest();
        request.topic = topic;
        request.payloadObj = payload;
        request.qos = 0;
        LinkKit.getInstance().getMqttClient().publish(request, new IConnectSendListener() {
            @Override
            public void onResponse(ARequest aRequest, AResponse aResponse) {
            }

            @Override
            public void onFailure(ARequest aRequest, AError aError) {
            }
        });
    }

    /**
     * Subscribe to a message
     * 
     * @param topic The topic to which you subscribe
     */
    public void subscribe(String topic) {
        MqttSubscribeRequest request = new MqttSubscribeRequest();
        request.topic = topic;
        LinkKit.getInstance().getMqttClient().subscribe(request, new IConnectSubscribeListener() {
            @Override
            public void onSuccess() {
            }

            @Override
            public void onFailure(AError aError) {
            }
        });
    }

}

The server-side code is as follows:

import java.io.UnsupportedEncodingException;

import org.apache.commons.codec.binary.Base64;

import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.exceptions.ServerException;
import com.aliyuncs.iot.model.v20180120.RRpcRequest;
import com.aliyuncs.iot.model.v20180120.RRpcResponse;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;

public class CheckDeviceStatusOnServer {

    // ===================Parameters that you must configure - Start===========================
    // The AccessKey ID of your Alibaba Cloud account.
    private static String accessKeyID = "";
    // The AccessKey secret of your Alibaba Cloud account.
    private static String accessKeySecret = "";
    // The ProductKey of the product. This is one of the device certificate parameters.
    private static String productKey = "";
    // The DeviceName of the device. This is one of the device certificate parameters.
    private static String deviceName = "";
    // The instance ID.
    private static String instanceId = "iot-******02";
    // ===================Parameters that you must configure - End===========================

    public static void main(String[] args) throws ServerException, ClientException, UnsupportedEncodingException {

        // -------------------------------------------------------------------
        // The message to be sent. You can customize it. We recommend using the current format.
        // -------------------------------------------------------------------
        // Field   | Type   | Desc
        // -------------------------------------------------------------------
        // id      | Object | Used to verify that the sent and received messages are the same. Ensure uniqueness at the service layer.
        // -------------------------------------------------------------------
        // version | String | The version number is fixed at 1.0.
        // -------------------------------------------------------------------
        // time    | Long   | The timestamp when the message is sent. You can use it to calculate the round-trip latency and evaluate the current communication quality.
        // -------------------------------------------------------------------
        String payload = "{\"id\":123, \"version\":\"1.0\",\"time\":" + System.currentTimeMillis() + "}";

        // Construct an RRPC request.
        RRpcRequest request = new RRpcRequest();
        request.setProductKey(productKey);
        request.setDeviceName(deviceName);
        request.setIotInstanceId(instanceId);
        request.setRequestBase64Byte(Base64.encodeBase64String(payload.getBytes()));
        request.setTimeout(5000);

        // Get the server-side request client.
        DefaultAcsClient client = getClient();

        // Initiate the RRPC request.
        RRpcResponse response = (RRpcResponse) client.getAcsResponse(request);

        // Process the RRPC response.
        // Do not check response.getSuccess(). This only indicates that the RRPC request was sent successfully. It does not mean the device received it and responded successfully.
        // The determination must be based on RrpcCode. For more information, see https://help.aliyun.com/document_detail/69797.html
        if (response != null && "SUCCESS".equals(response.getRrpcCode())) {
            if (payload.equals(new String(Base64.decodeBase64(response.getPayloadBase64Byte()), "UTF-8"))) {
                System.out.println("Device is online");
            } else {
                System.out.println("Device is offline1");
            }
        } else {
            System.out.println("Device is offline");
        }
    }

    public static DefaultAcsClient getClient() {

        DefaultAcsClient client = null;

        try {
            // In the following code, cn-shanghai is only an example. Replace it with the region ID of your IoT Platform service.
            IClientProfile profile = DefaultProfile.getProfile("cn-shanghai", accessKeyID, accessKeySecret);
            DefaultProfile.addEndpoint("cn-shanghai", "cn-shanghai", "Iot", "iot.cn-shanghai.aliyuncs.com");
            client = new DefaultAcsClient(profile);
        } catch (Exception e) {
            System.out.println("init client failed !! exception:" + e.getMessage());
        }

        return client;
    }
}
Note

The server must actively trigger the RRPC call to check whether a response is promptly received from the client.