ROS机器人只能在内网访问?遇到这样一个需求,希望在公司访问控制到客户的机器人,需要获得摄像头图像以及远程控制机器人。想到采用的方案有以下几种:
那么对比这三种方案很明显我们不想花钱做这个事情。我们采用Frp搭建内网穿透尝试该方案是否可行。
先决条件:
验证测试:
因为机器人不方便测试,我采用在虚拟机里面安装一个ROS,然后跑起来小乌龟,当用公网服务器能控制小乌龟进行移动则证明实验成功。
首先在公网服务器和虚拟机(机器人)中安装对应版本的frp
https://github.com/fatedier/frp/releases

我这里下载的是0.44.0版本,由于虚拟机和服务器都是x86架构的,则两个都下frp_0.44.0_linux_amd64.tar.gz,如果机器人是nvidia系列arm板子,则需下载frp_0.44.0_linux_arm64.tar.gz,下载好分别解压在服务器和机器人上。
frp服务端(服务器):
tar -zxvf frp_0.44.0_linux_amd64.tar.gz
mv frp_0.44.0_linux_amd64 frp
cd frp
#在frps.ini中配置
vim frps.ini
[common]
bind_port = 7000
dashboard_port = 7500 #web dashboard仪表盘
token = 12345678
dashboard_user = admin
dashboard_pwd = admin
vhost_http_port = 6001 #为了让web图像正常传输
#保存后执行
./frps -c frps.ini

frp客户端(机器人或者虚拟机):
tar -zxvf frp_0.44.0_linux_amd64.tar.gz
mv frp_0.44.0_linux_amd64 frp
cd frp
#在frpc.ini中配置
[common]
server_addr = 公网服务器IP
token = 12345678
server_port = 7000
[ssh]
type = tcp
local_ip = 内网机器人IP
local_port = 22
remote_port = 6000
[web]
type = http
local_ip = 内网机器人IP
local_port = 80
custom_domains = 公网服务器IP
[ws]
type = tcp
local_ip = 内网机器人IP
local_port = 9090
remote_port = 9090
#[camera]
#type = http
#local_ip = 内网机器人IP
#local_port = 8080
#remote_port = 6003
## 如果要穿透多个web服务器则需要二级域名 只用ip不可以
#custom_domains = 公网服务器IP
接下来开启两个终端,打开小乌龟节点,运行rosbridge,这样才能用web进行控制。
roslaunch rosbridge_server rosbridge_websocket.launch

在自己的电脑编写如下测试html代码:
DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script type="text/javascript" src="http://static.robotwebtools.org/EventEmitter2/current/eventemitter2.min.js">script>
<script type="text/javascript" src="http://static.robotwebtools.org/roslibjs/current/roslib.min.js">script>
<script type="text/javascript" type="text/javascript">
// Connecting to ROS
var ros = new ROSLIB.Ros({
url : 'ws://公网IP:9090'
});
var isconected=false;
//判断是否连接成功并输出相应的提示消息到web控制台
ros.on('connection', function() {
isconected=true;
console.log('Connected to websocket server.');
subscribe();
});
ros.on('error', function(error) {
isconected=false;
console.log('Error connecting to websocket server: ', error);
});
ros.on('close', function() {
isconected=false;
console.log('Connection to websocket server closed.');
unsubscribe();
});
// Publishing a Topic
var cmdVel = new ROSLIB.Topic({
ros : ros,
name : 'turtle1/cmd_vel',
messageType : 'geometry_msgs/Twist'
});//创建一个topic,它的名字是'/cmd_vel',,消息类型是'geometry_msgs/Twist'
var twist = new ROSLIB.Message({
linear : {
x : 0.0,
y : 0.0,
z : 0.0
},
angular : {
x : 0.0,
y : 0.0,
z : 0.0
}
});//创建一个message
function control_move(direction){
twist.linear.x = 0.0;
twist.linear.y = 0;
twist.linear.z = 0;
twist.angular.x = 0;
twist.angular.y = 0;
twist.angular.z = 0.0;
switch(direction){
case 'up':
twist.linear.x = 2.0;
break;
case 'down':
twist.linear.x = -2.0;
break;
case 'left':
twist.angular.z = 2.0;
break;
case 'right':
twist.angular.z = -2.0;
break;
}
cmdVel.publish(twist);//发布twist消息
}
var timer=null;
function buttonmove(){
var oUp=document.getElementById('up');
var oDown=document.getElementById('down');
var oLeft=document.getElementById('left');
var oRight=document.getElementById('right');
oUp.onmousedown=function ()
{
Move('up');
}
oDown.onmousedown=function ()
{
Move('down');
}
oLeft.onmousedown=function ()
{
Move('left');
}
oRight.onmousedown=function ()
{
Move('right');
}
oUp.onmouseup=oDown.onmouseup=oLeft.onmouseup=oRight.onmouseup=function ()
{
MouseUp ();
}
}
function keymove (event) {
event = event || window.event;/*||为或语句,当IE不能识别event时候,就执行window.event 赋值*/
console.log(event.keyCode);
switch (event.keyCode){/*keyCode:字母和数字键的键码值*/
/*65,87,68,83分别对应awds*/
case 65:
Move('left');
break;
case 87:
Move('up');
break;
case 68:
Move('right');
break;
case 83:
Move('down');
break;
default:
break;
}
}
var MoveTime=20;
function Move (f){
clearInterval(timer);
timer=setInterval(function (){
control_move(f)
},MoveTime);
}
function MouseUp ()
{
clearInterval(timer);
}
function KeyUp(event){
MouseUp();
}
window.onload=function ()
{
buttonmove();
document.onkeyup=KeyUp;
document.onkeydown=keymove;
}
// Subscribing to a Topic
var listener = new ROSLIB.Topic({
ros : ros,
name : '/turtle1/pose',
messageType : 'turtlesim/Pose'
});//创建一个topic,它的名字是'/turtle1/pose',,消息类型是'turtlesim/Pose',用于接收乌龟位置信息
var turtle_x=0.0;
var turtle_y=0.0;
function subscribe()//在连接成功后,控制div的位置,
{
listener.subscribe(function(message) {
turtle_x=message.x;
turtle_y=message.y;
document.getElementById("output").innerHTML = ('Received message on ' + listener.name +' x: ' + message.x+" ,y: "+message.y);
});
}
function unsubscribe()//在断开连接后,取消订阅
{
listener.unsubscribe();
}
script>
head>
<body>
<input type="button" value="前行" id="up">
<input type="button" value="后退" id="down">
<input type="button" value="左转" id="left">
<input type="button" value="右转" id="right">
<p id = "output">p>
body>
html>
websocket连接成功后,就可以用公网服务器操控机器人运动了。

图像显示的话也是一样的,需要开启web_video_server服务,然后暴露http的8080口。
如此,实现了公网地址远程操控内网机器人。