Controlling a Raspberry Pi Server Remotely

Alibaba Cloud
8 min readJun 24, 2019

--

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

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

Original Source

https://www.alibabacloud.com/blog/controlling-a-raspberry-pi-server-remotely_594924?spm=a2c41.13056867.0.0

--

--

Alibaba Cloud
Alibaba Cloud

Written by Alibaba Cloud

Follow me to keep abreast with the latest technology news, industry insights, and developer trends. Alibaba Cloud website:https://www.alibabacloud.com

No responses yet