草庐IT

ROS机器人外网远程控制(一种基于Frp内网穿透,使用websocket/http通信方式)

冷色调的夏天 2023-04-10 原文

ROS机器人只能在内网访问?遇到这样一个需求,希望在公司访问控制到客户的机器人,需要获得摄像头图像以及远程控制机器人。想到采用的方案有以下几种:

  • 采用frp进行内网穿透
  • 采用蒲公英进行组网
  • 添加一张4G网卡使其具有公网IP

那么对比这三种方案很明显我们不想花钱做这个事情。我们采用Frp搭建内网穿透尝试该方案是否可行。

先决条件:

  • 需要一台公网服务器(没钱的可以去白嫖移动云一个月)
  • 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口。
如此,实现了公网地址远程操控内网机器人。

有关ROS机器人外网远程控制(一种基于Frp内网穿透,使用websocket/http通信方式)的更多相关文章

  1. ruby - 如何模拟 Net::HTTP::Post? - 2

    是的,我知道最好使用webmock,但我想知道如何在RSpec中模拟此方法:defmethod_to_testurl=URI.parseurireq=Net::HTTP::Post.newurl.pathres=Net::HTTP.start(url.host,url.port)do|http|http.requestreq,foo:1endresend这是RSpec:let(:uri){'http://example.com'}specify'HTTPcall'dohttp=mock:httpNet::HTTP.stub!(:start).and_yieldhttphttp.shou

  2. ruby - 在 Windows 机器上使用 Ruby 进行开发是否会适得其反? - 2

    这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby​​-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub

  3. ruby - Net::HTTP 获取源代码和状态 - 2

    我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur

  4. Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting - 2

    1.错误信息:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:requestcanceledwhilewaitingforconnection(Client.Timeoutexceededwhileawaitingheaders)或者:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:TLShandshaketimeout2.报错原因:docker使用的镜像网址默认为国外,下载容易超时,需要修改成国内镜像地址(首先阿里

  5. ruby-on-rails - Rails - 从命名路由中提取 HTTP 动词 - 2

    Rails中有没有一种方法可以提取与路由关联的HTTP动词?例如,给定这样的路线:将“users”匹配到:“users#show”,通过:[:get,:post]我能实现这样的目标吗?users_path.respond_to?(:get)(显然#respond_to不是正确的方法)我最接近的是通过执行以下操作,但它似乎并不令人满意。Rails.application.routes.routes.named_routes["users"].constraints[:request_method]#=>/^GET$/对于上下文,我有一个设置cookie然后执行redirect_to:ba

  6. ruby-on-rails - Heroku 吃掉了我的自定义 HTTP header - 2

    我正在使用Heroku(heroku.com)来部署我的Rails应用程序,并且正在构建一个iPhone客户端来与之交互。我的目的是将手机的唯一设备标识符作为HTTPheader传递给应用程序以进行身份​​验证。当我在本地测试时,我的header通过得很好,但在Heroku上它似乎去掉了我的自定义header。我用ruby​​脚本验证:url=URI.parse('http://#{myapp}.heroku.com/')#url=URI.parse('http://localhost:3000/')req=Net::HTTP::Post.new(url.path)#boguspara

  7. ruby - 我的 Ruby IRC 机器人没有连接到 IRC 服务器。我究竟做错了什么? - 2

    require"socket"server="irc.rizon.net"port="6667"nick="RubyIRCBot"channel="#0x40"s=TCPSocket.open(server,port)s.print("USERTesting",0)s.print("NICK#{nick}",0)s.print("JOIN#{channel}",0)这个IRC机器人没有连接到IRC服务器,我做错了什么? 最佳答案 失败并显示此消息::irc.shakeababy.net461*USER:Notenoughparame

  8. ruby-on-rails - 使用 HTTP.get_response 检索 Facebook 访问 token 时出现 Rails EOF 错误 - 2

    我试图在我的网站上实现使用Facebook登录功能,但在尝试从Facebook取回访问token时遇到障碍。这是我的代码:ifparams[:error_reason]=="user_denied"thenflash[:error]="TologinwithFacebook,youmustclick'Allow'toletthesiteaccessyourinformation"redirect_to:loginelsifparams[:code]thentoken_uri=URI.parse("https://graph.facebook.com/oauth/access_token

  9. ruby - HTTP 请求中的用户代理,Ruby - 2

    我是Ruby的新手。我试过查看在线文档,但没有找到任何有效的方法。我想在以下HTTP请求botget_response()和get()中包含一个用户代理。有人可以指出我正确的方向吗?#PreliminarycheckthatProggitisupcheck=Net::HTTP.get_response(URI.parse(proggit_url))ifcheck.code!="200"puts"ErrorcontactingProggit"returnend#Attempttogetthejsonresponse=Net::HTTP.get(URI.parse(proggit_url)

  10. ruby - Faye WebSocket,关闭处理程序被触发后重新连接到套接字 - 2

    我有一个super简单的脚本,它几乎包含了FayeWebSocketGitHub页面上用于处理关闭连接的内容:ws=Faye::WebSocket::Client.new(url,nil,:headers=>headers)ws.on:opendo|event|p[:open]#sendpingcommand#sendtestcommand#ws.send({command:'test'}.to_json)endws.on:messagedo|event|#hereistheentrypointfordatacomingfromtheserver.pJSON.parse(event.d

随机推荐