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.
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:
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.
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.
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) {
}
});
}
}
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());
}
}
}
Building a Simple Entry-Exit Monitoring System on Alibaba Cloud IoT Platform
GXIC - February 25, 2019
Alibaba Developer - May 31, 2021
Alibaba Clouder - January 11, 2019
GXIC - January 7, 2019
GXIC - June 11, 2019
Hiteshjethva - October 31, 2019
Provides secure and reliable communication between devices and the IoT Platform which allows you to manage a large number of devices on a single IoT Platform.
Learn MoreA cloud solution for smart technology providers to quickly build stable, cost-efficient, and reliable ubiquitous platforms
Learn MoreAlibaba Cloud offers an accelerated global networking solution that makes distance learning just the same as in-class teaching.
Learn MoreMigrate your Internet Data Center’s (IDC) Internet gateway to the cloud securely through Alibaba Cloud’s high-quality Internet bandwidth and premium Mainland China route.
Learn MoreMore Posts by GXIC