×
Community Blog Controlling a Raspberry Pi Server Remotely

Controlling a Raspberry Pi Server Remotely

This article demonstrates the implementation of pseudo-Intranet penetration of Alibaba Cloud's IoT Platform to control a Raspberry Pi server remotely.

The Alibaba Cloud IoT Platform enables you to implement pseudo-Intranet penetration and remote control of Raspberry Pi servers without public IP addresses. This article uses remote control over a Raspberry Pi server as an example to demonstrate the implementation of pseudo-Intranet penetration and provides development code samples.

Background

Assume that you want to build a Raspberry Pi-based server to run some simple tasks, such as starting a script and downloading a file. However, if the Raspberry Pi server does not have a public IP address, you can only manage this server when you are either at the office or at home. If you use other Intranet penetration tools, you may have encountered frequent disconnection issues. To solve this problem, you can use Alibaba Cloud IoT Platform's RRPC (Revert Remote Procedure Call, or revert-RPC) feature with the JSch library to implement remote control over the Raspberry Pi server.

Remote control implementation steps

1

Use the IoT Platform to implement remote control over the Raspberry Pi server by following these steps:

  • Call the IoT Platform RRPC interface on a computer to send an SSH directive.
  • After receiving this directive, the IoT Platform sends this SSH directive to the Raspberry Pi server by using the MQTT protocol.
  • The server runs the SSH directive.
  • The server encapsulates the result of this SSH directive into an RRPC response and reports it to the IoT Platform by using the MQTT protocol.
  • The IoT Platform returns this RRPC response to the computer.

Note: The RRPC timeout is five seconds. If the server does not receive response in five seconds, a timeout error is thrown. If it takes a long time to run your directive, you can ignore the timeout error.

Download the SDKs and Demo

To implement remote control over this Raspberry Pi server on the IoT Platform, you need to develop the server-side SDK and the device-side SDK.

The following sections give examples of server-side and device-side SDK development.

Note: Code samples provided in this article only support simple Linux commands like uname, touch, and mv and do not support complex directives like editing files. To run complex directives, you can write your own code.

Device-side SDK Development

After downloading and installing the device-side SDK and downloading the SDK Demon, you need to add the project dependencies and the following Java files.

The project can be exported as a jar package to run on Raspberry Pi.

1.  Add dependencies to the pom.xml file.

<! -- Device-side 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 to run SSH directives.

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 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 to establish MQTT connections.

public class Device {

    /**
     * Establish a connection
     * 
     * @param productKey: product key
     * @param deviceName: device name
     * @param deviceSecret: device secret
     * @throws InterruptedException
     */
    public static void connect(String productKey, String deviceName, String deviceSecret) throws InterruptedException {

        // Initialization parameters
        LinkKitInitParams params = new LinkKitInitParams();

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

        // Configure initialization device certificate information and pass the following:
        DeviceInfo deviceInfo = new DeviceInfo();
        deviceInfo.productKey = productKey;
        deviceInfo.deviceName = deviceName;
        deviceInfo.deviceSecret = deviceSecret;
        params.deviceInfo = deviceInfo;

        // Initialization
        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 you perform the following steps only after the initialization is completed. You can adjust the latency as needed for this step
        Thread.sleep(2000);
    }

    /**
     * Publish messages
     * 
     * @param topic: the topic of the message to be sent
     * @param payload: the content of the message to be sent
     */
    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 of the subscribed message
     */
    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 of the unsubscribed message
     */
    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) {
            }
        });
    }

    /**
     * Disconnect
     */
    public static void disconnect() {
        // Deinitialize
        LinkKit.getInstance().deinit();
    }

}

5.  Add the SSHDevice.java file. The SSHDevice.java file includes the main method. This file is used to receive revert-RPC directives, invokes SSHShell to run SSH directives, and return revert-RPC response. Device certificate information (ProductKey, DeviceName, and DeviceSecret) and the SSH account and password are required in SSHDevice.java.

public class SSHDevice {

    // ===================The list of required parameters begins here===========================
    // productKey
    private static String productKey = "";
    // 
    private static String deviceName = "";
    // deviceSecret
    private static String deviceSecret = "";
    // The topic of the communication message (You do not need to create or define one. It is directly available.)
    private static String rrpcTopic = "/sys/" + productKey + "/" + deviceName + "/rrpc/request/+";
    // The domain name or IP that SSH will access
    private static String host = "127.0.0.1";
    // SSH username
    private static String username = "";
    // SSH password
    private static String password = "";
    // SSH port number
    private static int port = 22;
    // ===================The list of required parameters ends here===========================

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

        // Listen to downlink data
        registerNotifyListener();

        // Establish the connection
        Device.connect(productKey, deviceName, deviceSecret);

        // Subscribe to topics
        Device.subscribe(rrpcTopic);
    }

    public static void registerNotifyListener() {
        LinkKit.getInstance().registerOnNotifyListener(new IConnectNotifyListener() {
            @Override
            public boolean shouldHandle(String connectId, String topic) {
                // Only process messages with a specific topic
                if (topic.contains("/rrpc/request/")) {
                    return true;
                } else {
                    return false;
                }
            }

            @Override
            public void onNotify(String connectId, String topic, AMessage aMessage) {
                // Receive revert-RPC requests and return revert-RPC response
                try {
                    // Run remote commands
                    String payload = new String((byte[]) aMessage.getData(), "UTF-8");
                    SSHShell sshExecutor = new SSHShell(host, username, password, port);
                    sshExecutor.execute(payload);

                    // Obtain command echo
                    StringBuffer sb = new StringBuffer();
                    Vector<String> stdout = sshExecutor.getStdout();
                    for (String str : stdout) {
                        sb.append(str);
                        sb.append("\n");
                    }

                    // Return the echo to the server side
                    String response = topic.replace("/request/", "/response/");
                    Device.publish(response, sb.toString());
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }
            }

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

}

Server-side SDK Development

After downloading and installing the server-side SDK and downloading the SDK Demon, you need to add the project dependencies and the following Java files.

1.  Add dependencies to the pom.xml file.

<! -- Server-side 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 to invoke the open interfaces on the IoT Platform.

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 Open API Client failed !! exception:" + e.getMessage());
        }

        return client;
    }

}

3.  Add the SSHCommandSender.java file. The SSHCommandSender.java file includes the main method. The file is used to send SSH directives and receive response to SSH directives. Your account AccessKey, device certificate information (ProductKey and DeviceName), and SSH directives are required in SSHCommandSender.java.

public class SSHCommandSender {

    // ===================The list of required parameters begins here===========================
    // User account AccessKey
    private static String accessKeyID = "";
    // User account AccesseKeySecret
    private static String accessKeySecret = "";
    // productKey
    private static String productKey = "";
    // deviceName
    private static String deviceName = "";
    // ===================The list of required parameters ends here===========================

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

        // Linux remote command
        String payload = "uname -a";

        // Build revert-RPC requests
        RRpcRequest request = new RRpcRequest();
        request.setProductKey(productKey);
        request.setDeviceName(deviceName);
        request.setRequestBase64Byte(Base64.encodeBase64String(payload.getBytes()));
        request.setTimeout(5000);

        // Obtain the server-side request client
        DefaultAcsClient client = OpenApiClient.getClient(accessKeyID, accessKeySecret);

        // Initiate a revert-RPC request
        RRpcResponse response = (RRpcResponse) client.getAcsResponse(request);

        // Process the revert-RPC response
        // response.getSuccess() indicates that the revert-RPC request is sent successfully. It does not indicate that the device has successfully received the request or that the response is successful.
        // Determination should be made based on RrpcCode. For more information, visit https://help.aliyun.com/document_detail/69797.html
        if (response ! = null && "SUCCESS".equals(response.getRrpcCode())) {
            // Echo
            System.out.println(new String(Base64.decodeBase64(response.getPayloadBase64Byte()), "UTF-8"));
        } else {
            // Echo fails and RrpcCode is printed
            System.out.println(response.getRrpcCode());
        }
    }

}
0 0 0
Share on

GXIC

16 posts | 3 followers

You may also like

Comments