IoT Platform allows you to implement pseudo-Intranet penetration to remotely control a Raspberry Pi server that does not have a public IP address. This topic describes how to use IoT Platform to remotely control a Raspberry Pi server, and provides sample code.

Background information

For example, you use Raspberry Pi to build a server in your company or at home to run some simple tasks, such as starting a script or downloading files. If the Raspberry Pi server does not have a public IP address and you are not in the company or at home, you cannot manage the server. If you use other Intranet penetration tools, disconnections may frequently occur. To resolve this issue, you can use the RRPC feature of IoT Platform together with the JSch library to remotely control the Raspberry Pi server.

Note In this example, a product and a device in a public instance are used.

Process

Connect a Raspberry Pi server to IoT Platform

The following process shows how to use IoT Platform to remotely control a Raspberry Pi server:

  1. Call the IoT Platform RRPC operation on your computer to send a Secure Shell (SSH) command.
  2. After IoT Platform receives the SSH command, IoT Platform sends the SSH command to the Raspberry Pi server over Message Queuing Telemetry Transport (MQTT).
  3. The system runs the SSH command on the server.
  4. The system encapsulates the result of the SSH command as an RRPC response on the server and sends the response to IoT Platform over MQTT.
  5. IoT Platform sends the RRPC response to your computer.
Note The RRPC timeout period is 5 seconds. If IoT Platform does not receive a response from the Raspberry Pi server within 5 seconds, a timeout error occurs. If you send a command that requires a long period of time to process, ignore the timeout error message.

Download SDKs and sample code

Before you can remotely control the Raspberry Pi server, develop an IoT Platform SDK and Link SDK.

The following sample code shows how to develop the IoT Platform SDK and Link SDK.

Note The sample code supports only simple Linux commands, such as uname, touch, and mv. Complex commands, such as the commands that are used to modify files are not supported. To use complex commands, write code based on your business requirements.

Develop the Link SDK

After you install the Link SDK and download the sample code, add project dependencies and the required Java files.

The project can be exported as a JAR package and run on the Raspberry Pi server.

  1. Add the following dependencies to the pom.xml file:
    <! -- Link SDK -->
    <dependency>
        <groupId>com.aliyun.alink.linksdk</groupId>
        <artifactId>iot-linkkit-java</artifactId>
        <version>1.1.0</version>
        <scope>compile</scope>
    </dependency>
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>2.8.1</version>
        <scope>compile</scope>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.83</version>
        <scope>compile</scope>
    </dependency>
    
    <! -- SSH client -->
    <!-- https://mvnrepository.com/artifact/com.jcraft/jsch -->
    <dependency>
        <groupId>com.jcraft</groupId>
        <artifactId>jsch</artifactId>
        <version>0.1.55</version>
    </dependency>
  2. Create a file named SSHShell.java to run SSH commands.
    public class SSHShell {
    
        private String host;
    
        private String username;
    
        private String password;
    
        private int port;
    
        private Vector<String> stdout;
    
        public SSHShell(final String ipAddress, final String username, final String password, final int port) {
            this.host = ipAddress;
            this.username = username;
            this.password = password;
            this.port = port;
            this.stdout = new Vector<String>();
        }
    
        public int execute(final String command) {
    
            System.out.println("ssh command: " + command);
    
            int returnCode = 0;
            JSch jsch = new JSch();
            SSHUserInfo userInfo = new SSHUserInfo();
    
            try {
                Session session = jsch.getSession(username, host, port);
                session.setPassword(password);
                session.setUserInfo(userInfo);
                session.connect();
    
                Channel channel = session.openChannel("exec");
                ((ChannelExec) channel).setCommand(command);
    
                channel.setInputStream(null);
                BufferedReader input = new BufferedReader(new InputStreamReader(channel.getInputStream()));
    
                channel.connect();
    
                String line = null;
                while ((line = input.readLine()) != null) {
                    stdout.add(line);
                }
                input.close();
    
                if (channel.isClosed()) {
                    returnCode = channel.getExitStatus();
                }
    
                channel.disconnect();
                session.disconnect();
            } catch (JSchException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            return returnCode;
        }
    
        public Vector<String> getStdout() {
            return stdout;
        }
    
    }
  3. Create a file named SSHUserInfo.java to verify the SSH username and password.
    public class SSHUserInfo implements UserInfo {
    
        @Override
        public String getPassphrase() {
            return null;
        }
    
        @Override
        public String getPassword() {
            return null;
        }
    
        @Override
        public boolean promptPassphrase(final String arg0) {
            return false;
        }
    
        @Override
        public boolean promptPassword(final String arg0) {
            return false;
        }
    
        @Override
        public boolean promptYesNo(final String arg0) {
            if (arg0.contains("The authenticity of host")) {
                return true;
            }
            return false;
        }
    
        @Override
        public void showMessage(final String arg0) {
        }
    
    }
  4. Create a file named Device.java to establish an MQTT connection.
    public class Device {
    
        /**
         * Establish a connection.
         * 
         * @param productKey The ProductKey of the product.
         * @param deviceName The DeviceName of the device.
         * @param deviceSecret The DeviceSecret of the device.
         * @throws InterruptedException
         */
        public static void connect(String productKey, String deviceName, String deviceSecret) throws InterruptedException {
    
            // Initialize parameters.
            LinkKitInitParams params = new LinkKitInitParams();
    
            // Configure the required parameters to establish an MQTT connection.
            IoTMqttClientConfig config = new IoTMqttClientConfig();
            config.productKey = productKey;
            config.deviceName = deviceName;
            config.deviceSecret = deviceSecret;
            params.mqttClientConfig = config;
    
            // Specify the certificate information about the device.
            DeviceInfo deviceInfo = new DeviceInfo();
            deviceInfo.productKey = productKey;
            deviceInfo.deviceName = deviceName;
            deviceInfo.deviceSecret = deviceSecret;
            params.deviceInfo = deviceInfo;
    
            // Initialize a client.
            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 !!");
                }
            });
    
            // Before you perform the following steps, make sure that the initialization is complete. You can specify a reasonable value based on your business scenario.
            Thread.sleep(2000);
        }
    
        /**
         * Send a message.
         * 
         @param topic The topic to which the message is sent.
         * @param payload The content of the message.
         */
        public static 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 messages.
         * 
         * @param topic The topic whose messages to which you want to subscribe.
         */
        public static void subscribe(String topic) {
            MqttSubscribeRequest request = new MqttSubscribeRequest();
            request.topic = topic;
            request.isSubscribe = true;
            LinkKit.getInstance().getMqttClient().subscribe(request, new IConnectSubscribeListener() {
                @Override
                public void onSuccess() {
                }
    
                @Override
                public void onFailure(AError aError) {
                }
            });
        }
    
        /**
         * Unsubscribe from messages.
         * 
         * @param topic The topic whose messages from which you want to unsubscribe.
         */
        public static void unsubscribe(String topic) {
            MqttSubscribeRequest request = new MqttSubscribeRequest();
            request.topic = topic;
            request.isSubscribe = false;
            LinkKit.getInstance().getMqttClient().unsubscribe(request, new IConnectUnscribeListener() {
                @Override
                public void onSuccess() {
                }
    
                @Override
                public void onFailure(AError aError) {
                }
            });
        }
    
        /**
         * Terminate the connection.
         */
        public static void disconnect() {
            // Deinitialize the parameters.
            LinkKit.getInstance().deinit();
        }
    
    }
  5. Create a file named SSHDevice.java. The SSHDevice.java file includes the main method that is used to receive RRPC commands. You can also call SSHShell to run SSH commands, and return RRPC responses. In the SSHDevice.java file, you must enter the device certificate information and the SSH username and password. The device certificate information includes the ProductKey, DeviceName, and DeviceSecret.
    public class SSHDevice {
    
        // ===================The start of the segment in which you configure the required parameters.===========================
        // The ProductKey of the product.
        private static String productKey = "";
        // 
        private static String deviceName = "";
        // The DeviceSecret of the device.
        private static String deviceSecret = "";
        // The topic that you want to use for messaging. You can use the topic without the need to create or define the topic.
        private static String rrpcTopic = "/sys/" + productKey + "/" + deviceName + "/rrpc/request/+";
        // The endpoint or the IP address to which the SSH client needs to access.
        private static String host = "127.0.0.1";
        // The username that is used to log on to the SSH client.
        private static String username = "";
        // The password for the username.
        private static String password = "";
        // The number of the port that is used to log on to the SSH client.
        private static int port = 22;
        // ===================The end of the segment in which you configure the required parameters.===========================
    
        public static void main(String[] args) throws InterruptedException {
    
            // Listen to downstream data.
            registerNotifyListener();
    
            // Establish a connection.
            Device.connect(productKey, deviceName, deviceSecret);
    
            // Subscribe to a topic.
            Device.subscribe(rrpcTopic);
        }
    
        public static void registerNotifyListener() {
            LinkKit.getInstance().registerOnNotifyListener(new IConnectNotifyListener() {
                @Override
                public boolean shouldHandle(String connectId, String topic) {
                    // Process messages only from specific topics.
                    if (topic.contains("/rrpc/request/")) {
                        return true;
                    } else {
                        return false;
                    }
                }
    
                @Override
                public void onNotify(String connectId, String topic, AMessage aMessage) {
                    // Receive RRPC requests and return RRPC responses.
                    try {
                        // Run a remote command.
                        String payload = new String((byte[]) aMessage.getData(), "UTF-8");
                        SSHShell sshExecutor = new SSHShell(host, username, password, port);
                        sshExecutor.execute(payload);
    
                        // Obtain the output of the command.
                        StringBuffer sb = new StringBuffer();
                        Vector<String> stdout = sshExecutor.getStdout();
                        for (String str : stdout) {
                            sb.append(str);
                            sb.append("\n");
                        }
    
                        // Return the output to the server.
                        String response = topic.replace("/request/", "/response/");
                        Device.publish(response, sb.toString());
                    } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                    }
                }
    
                @Override
                public void onConnectStateChange(String connectId, ConnectState connectState) {
                }
            });
        }
    
    }

Develop the IoT Platform SDK

After you install the IoT Platform SDK and download the sample code, add project dependencies and the required Java files.

  1. Add the following dependencies to the pom.xml file:
    Notice For more information about the latest version of the IoT Platform SDK, see Use IoT Platform SDK for Java.
    <! -- IoT Platform SDK -->
    <dependency>
        <groupId>com.aliyun</groupId>
        <artifactId>aliyun-java-sdk-iot</artifactId>
        <version>7.38.0</version>
    </dependency>
    <dependency>
        <groupId>com.aliyun</groupId>
        <artifactId>aliyun-java-sdk-core</artifactId>
        <version>4.5.6</version>
    </dependency>
    
    <!-- commons-codec -->
    <dependency>
        <groupId>commons-codec</groupId>
        <artifactId>commons-codec</artifactId>
        <version>1.8</version>
    </dependency>
  2. Create a file named OpenApiClient.java to call IoT Platform API operations.
    public class OpenApiClient {
    
        private static DefaultAcsClient client = null;
    
        public static DefaultAcsClient getClient(String accessKeyID, String accessKeySecret) {
    
            if (client != null) {
                return client;
            }
    
            try {
                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("create OpenAPI Client failed !! exception:" + e.getMessage());
            }
    
            return client;
        }
    
    }
  3. Create a file named SSHCommandSender.java. The SSHCommandSender.java file includes the main method that is used to send SSH commands and receive responses to SSH commands. In the SSHCommandSender.java file, you must specify the device certificate information, the AccessKey pair of your Alibaba Cloud account, and SSH commands. The device certificate information includes the ProductKey, DeviceName, and DeviceSecret.
    public class SSHCommandSender {
    
    
        // ===================The start of the segment in which you configure the required parameters.===========================
    
        // 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.
    
        private static String productKey = "";
    
        // The DeviceName of the device.
    
        private static String deviceName = "";
    
        // ===================The end of the segment in which you configure the required parameters.===========================
    
    
        public static void main(String[] args) throws ServerException, ClientException, UnsupportedEncodingException {
    
    
            // The remote Linux command.
    
            String payload = "uname -a";
    
    
            // Create an RRPC request.
    
            RRpcRequest request = new RRpcRequest();
    
            request.setProductKey(productKey);
    
            request.setDeviceName(deviceName);
    
            request.setRequestBase64Byte(Base64.encodeBase64String(payload.getBytes()));
    
            request.setTimeout(5000);
    
    
            // The client that is used to receive requests from the server.
    
            DefaultAcsClient client = OpenApiClient.getClient(accessKeyID, accessKeySecret);
    
    
            // Send an RRPC request.
    
            RRpcResponse response = (RRpcResponse) client.getAcsResponse(request);
    
    
            // Process RRPC responses.
    
            // The result that is returned after the execution of the response.getSuccess() function indicates only that the RRPC request is sent. The returned response does not indicate whether the request is received by the device or whether the device returned a response based on the result.
    
            // You must check the result based on the RRPC response code that is returned. For more information, see https://www.alibabacloud.com/help/doc-detail/69797.htm.
    
            if (response != null && "SUCCESS".equals(response.getRrpcCode())) {
    
                // The output.
    
                System.out.println(new String(Base64.decodeBase64(response.getPayloadBase64Byte()), "UTF-8"));
    
            } else {
    
                // Failed to obtain the output and print the related RRPC response code.
    
                System.out.println(response.getRrpcCode());
    
            }
    
        }
    
    
    }