IoT Platform allows you to implement pseudo-Intranet penetration to remotely control Raspberry Pi servers that have no public IP addresses. This article describes how to remotely control Raspberry Pi servers from IoT Platform, and provides sample code.

Background information

Assume that you use Raspberry Pi to build a server at your company or home to run some simple tasks, such as starting a script or downloading files. However, the Raspberry Pi server does not have a public IP address. You cannot control the server when you are not in the company or at home. If you use other Intranet penetration tools, disconnection issues may frequently occur. To solve this problem, you can use RRPC feature of IoT Platform with the JSch library to implement remote control over the Raspberry Pi server.

Process

Connect a Raspberry Pi server to IoT Platform

To control a Raspberry Pi server from IoT Platform, perform the following steps:

  1. Call the IoT Platform RRPC operation on your computer to send an SSH command.
  2. After IoT Platform receives the SSH command, send the SSH command to the Raspberry Pi server over MQTT.
  3. Run the SSH command on your server.
  4. Encapsulate the result as an RRPC response and send the response to IoT Platform over MQTT.
  5. Send the RRPC response from IoT Platform to your computer.
Note The RRPC timeout period is 5 seconds. If IoT Platform does not receive a reply from the Raspberry Pi server within 5 seconds, a timeout error occurs. If you send a command that requires much time to process, ignore the timeout error message.

Download SDKs and sample code

To remotely control the Raspberry Pi server, you must first develop IoT Platform SDK and Link SDK.

The following sections provide the sample code to develop 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 to edit files, are not supported. To implement complex commands, you must write code as needed.

Develop Link SDK

After you install 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.40</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. Add the SSHShell.java file that is used 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. Add the SSHUserInfo.java file that is used to verify the SSH account 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. Add the Device.java file that is used to establish an MQTT connection.
    public class Device {
    
        /**
         * Establish a connection.
         * 
         * @param productKey: the ProductKey.
         * @param deviceName: the DeviceName.
         * @param deviceSecret: the DeviceSecret.
         * @throws InterruptedException
         */
        public static void connect(String productKey, String deviceName, String deviceSecret) throws InterruptedException {
    
            // Initialize parameters.
            LinkKitInitParams params = new LinkKitInitParams();
    
            // Set MQTT parameters for initialization.
            IoTMqttClientConfig config = new IoTMqttClientConfig();
            config.productKey = productKey;
            config.deviceName = deviceName;
            config.deviceSecret = deviceSecret;
            params.mqttClientConfig = config;
    
            // Specify device certificate information.
            DeviceInfo deviceInfo = new DeviceInfo();
            deviceInfo.productKey = productKey;
            deviceInfo.deviceName = deviceName;
            deviceInfo.deviceSecret = deviceSecret;
            params.deviceInfo = deviceInfo;
    
            // Initialize the SDK.
            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 !!");
                }
            });
    
            // Perform the subsequent operations after the initialization is complete. You can increase the sleep time as needed.
            Thread.sleep(2000);
        }
    
        /**
         *  Publish a message.
         * 
         * @param topic: The topic to which the message is published.
         * @param payload: The message payload.
         */
        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 a topic.
         * 
         * @param topic: The topic to subscribe to.
         */
        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 a topic.
         * 
         * @param topic: The topic to unsubscribe from
         */
        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) {
                }
            });
        }
    
        /**
         * End the connection.
         */
        public static void disconnect() {
            // Deinitialize the SDK.
            LinkKit.getInstance().deinit();
        }
    
    }
  5. Add the SSHDevice.java file. 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 account and password. The device certificate information includes ProductKey, DeviceName, and DeviceSecret.
    public class SSHDevice {
    
        // ===================Specify required parameters===========================    // The ProductKey.    private static String productKey = "";    //     private static String deviceName = "";    // The DeviceSecret.    private static String deviceSecret = "";    // The message communication topic. You can directly use the topic without creating or defining the topic.    private static String rrpcTopic = "/sys/" + productKey + "/" + deviceName + "/rrpc/request/+";    // The domain name or IP address that you want to access by using SSH.    private static String host = "127.0.0.1";    // The SSH username.    private static String username = "";    // The SSH password.    private static String password = "";    // The SSH port    private static int port = 22;    // ===================End===========================    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 only messages in the specified topic.                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 the SSH command.                    String payload = new String((byte[]) aMessage.getData(), "UTF-8");                    SSHShell sshExecutor = new SSHShell(host, username, password, port);                    sshExecutor.execute(payload);                    // Obtain the result.                   StringBuffer sb = new StringBuffer();                    Vector<String> stdout = sshExecutor.getStdout();
                        for (String str : stdout) {
                            sb.append(str);
                            sb.append("\n");
                        }
    
                        // Return the result 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 IoT Platform SDK

After you install 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:
    
                                <! -- IoT Platform SDK -->
    <dependency>
        <groupId>com.aliyun
                                </groupId>
                                <artifactId>aliyun-java-sdk-iot</artifactId>
        <version>6.5.0</version>
    </dependency>
    <dependency>
        <groupId>com.aliyun
                                </groupId>
                                <artifactId>aliyun-java-sdk-core</artifactId>
        <version>3.5.1</version>
    </dependency>
    
    <!-- commons-codec -->
    <dependency>
        <groupId>commons-codec
                                </groupId>
                                <artifactId>commons-codec</artifactId>
        <version>1.8</version>
    </dependency>
                            
  2. Add the OpenApiClient.java file that is used 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. Add the SSHCommandSender.java file. 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 enter the device certificate information and the SSH account and password. The device certificate information includes ProductKey, DeviceName, and DeviceSecret.
    public class SSHCommandSender {
    
    
        // ===================Specify required parameters===========================
    
        // The AccessKey ID.
    
        private static String accessKeyID = "";
    
        // The AccesseKey secret.
    
        private static String accessKeySecret = "";
    
        // The ProductKey.
    
        private static String productKey = "";
    
        // The DeviceName.
    
        private static String deviceName = "";
    
        // ===================End===========================
    
    
        public static void main(String[] args) throws ServerException, ClientException, UnsupportedEncodingException {
    
    
            // The 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);
    
    
            // Obtain the information about the client that is connected to the Raspberry Pi server.
    
            DefaultAcsClient client = OpenApiClient.getClient(accessKeyID, accessKeySecret);
    
    
            // Initiate an RRPC request.
    
            RRpcResponse response = (RRpcResponse) client.getAcsResponse(request);
    
    
            // Process an RRPC response.
    
            // "response.getSuccess()" only indicates that the RRPC request has been sent. It does not indicate that the device has received the RRPC request and has returned a response.
    
            // Identify whether the message has been received and a response has been returned based on the RrpcCode value. For more information, visit https://www.alibabacloud.com/help/doc-detail/69797.html.
    
            if (response != null && "SUCCESS".equals(response.getRrpcCode())) {
    
                // Obtain the response.
    
                System.out.println(new String(Base64.decodeBase64(response.getPayloadBase64Byte()), "UTF-8"));
    
            } else {
    
                // An error occurred while obtaining the response and an RRPC code is returned.
    
                System.out.println(response.getRrpcCode());
    
            }
    
        }
    
    
    }