Skip to content

Instantly share code, notes, and snippets.

@asmodehn
Created August 25, 2016 05:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save asmodehn/41bccf3949fd7bf2d4e710e497e2ddc9 to your computer and use it in GitHub Desktop.
Save asmodehn/41bccf3949fd7bf2d4e710e497e2ddc9 to your computer and use it in GitHub Desktop.
Rostful + rosbridge from @dhirajdhule
I have done little hack thing. There could be places where it can be cleaned up a bit.
I Imported 'rosbridge_websocket' tornado app into rostfuls server.py.
* ( make sure that rosbridge websocket python app is on your pythonpath. If you installed rosbridge using apt-get then make sure that
echo $PYTHONPATH shows /opt/ros/indigo/lib/python2.7/dist-packages)
And added rosapi(from rosbridge launchfile) node to the rostfuls launch file.
To launch I'm just launching above edited rostful.launch. The server.py will start rosbridge app.
So any configurations that we do using rosbridge launch file wont work. Port can be selected from rostful launcher.
All the socket urls are placed inside /websocket . So if you try to test websocket thing then do here localhost:port/websocket/
and rest urls are served from /
<launch>
<!-- ******************************* Arguments ******************************* -->
<arg name="rostful_port" default="9090"/>
<arg name="rostful_server" default="tornado"/>
<arg name="topics" default="['/listener']" />
<arg name="services" default="[]" />
<arg name="actions" default="[]" />
<arg name="enable_rocon" default="false"/> <!-- NOT WORKING ANYMORE -->
<arg name="rapps_namespaces" default="[]" /> <!-- TODO : default should be to watch all -> wildcard support needed -->
<!-- rostful dev server -->
<node pkg="rostful" name="rostful" type="devserver" args="-p $(arg rostful_port) -s $(arg rostful_server)">
<param name="topics" value="$(arg topics)" type="str" />
<param name="services" value="$(arg services)" type="str" />
<param name="actions" value="$(arg actions)" type="str" />
<param name="enable_rocon" value="$(arg enable_rocon)" type="bool" />
<param name="rapps_namespaces" value="$(arg rapps_namespaces)" type="str"/>
</node>
<node name="rosapi" pkg="rosapi" type="rosapi_node" />
</launch>
# -*- coding: utf-8 -*-
from __future__ import absolute_import
import os
import sys
import logging
try:
import rostful_node
except Exception, e:
print "rostful_node module is not accessible in sys.path. It is required to run rostful."
print "Exception caught : ", e
print "sys.path = %r", sys.path
raise
from . import flask_cfg
from flask import Flask, session, request, flash, abort, make_response, render_template, url_for, jsonify, redirect, send_from_directory
from flask.ext.login import login_user , logout_user , current_user , login_required , LoginManager
import flask_security as security
import flask_cors as cors
import flask_restful as restful
import flask_login as login
from tornado.log import enable_pretty_logging
from tornado.web import Application, FallbackHandler, RequestHandler
from tornado.wsgi import WSGIContainer
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from . import db_models
from .db_models import db, User
from .flask_views import FrontEnd, BackEnd, Rostful
import threading
import subprocess
import uuid
background_scripts = {}
class Server(object):
#TODO : pass config file from command line here
def __init__(self):
self.app = Flask('rostful',
static_folder=os.path.join(os.path.dirname(os.path.abspath(__file__)), 'static'),
template_folder=os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates'),
instance_relative_config=True
)
self.app.config.from_object(flask_cfg.Development)
#TODO : flexible config by chosing file
#TODO : flexible config by getting file from instance folder
#TODO : flexible config by getting env var
self.login_manager= LoginManager()
self.login_manager.init_app(self.app)
self.login_manager.login_view='login'
@self.login_manager.user_loader
def load_user(id):
return User.query.get(int(id))
#initializes DB (needed here to allow migrations without launching flask server)
db.init_app(self.app)
self.db = db
# # Setup Flask-Security
# self.user_datastore = security.SQLAlchemyUserDatastore(self.db, db_models.User, db_models.Role)
# self.security = security.Security(self.app, self.user_datastore)
# One of the simplest configurations. Exposes all resources matching /* to
# CORS and allows the Content-Type header, which is necessary to POST JSON
# cross origin.
self.cors = cors.CORS(self.app, resources=r'/*', allow_headers='*')
# REST API extended to render exceptions as json
# https://gist.github.com/grampajoe/6529609
# http://www.wiredmonk.me/error-handling-and-logging-in-flask-restful.html
class Api(restful.Api):
def handle_error(self, e):
# Attach the exception to itself so Flask-Restful's error handler
# tries to render it.
if not hasattr(e, 'data'): # TODO : fix/improve this
e.data = e
return super(Api, self).handle_error(e)
# TMP not sure which one is best
self.api = restful.Api(self.app)
#self.api = Api(self.app)
@property
def logger(self):
return self.app.logger
def _setup(self, ros_node_client, debug=False):
self.ros_node_client = ros_node_client
rostfront = FrontEnd.as_view('frontend', self.ros_node_client, self.logger, debug)
rostback = BackEnd.as_view('backend', self.ros_node_client, self.logger, debug)
rostful = Rostful.as_view('rostful', self.ros_node_client, self.logger, debug)
# self.app.add_url_rule('/favicon.ico', redirect_to=url_for('static', filename='favicon.ico'))
# TODO : improve with https://github.com/flask-restful/flask-restful/issues/429
self.app.add_url_rule('/', 'rostfront', view_func=rostfront, methods=['GET'])
#TODO : put everything under robot/worker name here ( so we can evolve to support multiple workers )
self.app.add_url_rule('/<path:rosname>', 'rostfront', view_func=rostfront, methods=['GET'])
self.app.add_url_rule('/ros/<path:rosname>', 'rostback', view_func=rostback, methods=['GET', 'POST'])
#TMP -> replace by using rosapi
self.app.add_url_rule('/rostful', 'rostful', view_func=rostful, methods=['GET'])
self.app.add_url_rule('/rostful/<path:rostful_name>', 'rostful', view_func=rostful, methods=['GET'])
def launch(self, host='127.0.0.1', port=8080, ros_args='', serv_type='flask'):
print host, port
from rosbridge_server import RosbridgeWebSocket
#One RostfulNode is needed for Flask.
#TODO : check if still true with multiple web process
with rostful_node.rostful_ctx(name='rostful', argv=ros_args) as node_ctx:
self._setup(node_ctx.client, False if serv_type == 'tornado' else True)
import socket # just to catch the "Address already in use error"
port_retries = 1
while port_retries > 0: # keep trying
try:
if serv_type == 'flask':
rostful_server.app.logger.info('Starting Flask server on port %d', port)
# debug is needed to investigate server errors.
# use_reloader set to False => killing the ros node also kills the server child.
rostful_server.app.run(
host=host,
port=port,
debug=True,
use_reloader=False,
)
elif serv_type == 'tornado':
import rospy
from tornado.ioloop import IOLoop
def shutdown_hook():
IOLoop.instance().stop()
rospy.init_node("rosbridge_websocket")
rospy.on_shutdown(shutdown_hook) # register shutdown hook to stop the server
rostful_server.app.logger.info('Starting Tornado server on port %d', port)
#enable_pretty_logging()
rostwsgi = WSGIContainer(self.app)
application = Application([(r"/websocket", RosbridgeWebSocket),(r".*", FallbackHandler, dict(fallback=rostwsgi))])
#application = Application([(r"/websocket", RosbridgeWebSocket)])
#application.listen(port)
#port2 = port + 1
http_server = HTTPServer(application)
#http_server = HTTPServer(WSGIContainer(self.app))
http_server.listen(port)
IOLoop.instance().start()
break
except socket.error, msg:
port_retries -= 1
port += 1
rostful_server.app.logger.error('Socket Error : {0}'.format(msg))
#TODO: if port = default value, catch the "port already in use" exception and increment port number, and try again
# Creating THE only instance of Server.
rostful_server = Server()
# Setting up error handlers
@rostful_server.app.errorhandler(404)
def page_not_found(error):
rostful_server.app.logger.error('Web Request ERROR 404 : %r', error)
return render_template('error.html', error=error), 404
@rostful_server.app.route('/flytconsole')
def flytConsole():
return send_from_directory(rostful_server.app.static_folder, 'FlytConsole/FlytConsole.html')
@rostful_server.app.route('/register' , methods=['GET','POST'])
def register():
if request.method == 'GET':
return render_template('register.html')
user = User(request.form['username'] , request.form['password'],request.form['email'])
db.session.add(user)
db.session.commit()
flash('User successfully registered')
return render_template('login.html')
#@rostful_server.app.route('/login',methods=['GET','POST'])
#def login():
# if request.method == 'GET':
# return render_template('login.html')
# return render_template('login.html')
@rostful_server.app.route('/login',methods=['GET','POST'])
def login():
if request.method == 'GET':
return render_template('login.html')
username = request.form['username']
password = request.form['password']
registered_user = User.query.filter_by(username=username,password=password).first()
if registered_user is None:
#flash('Username or Password is invalid' , 'error')
return redirect(url_for('login'))
login_user(registered_user)
flash('Logged in successfully')
return redirect(url_for('flytConsole'))
#def run_script(id):
# subprocess.call(["/home/flytpod/FlytOS/src/flytOS/flyt_core/core_api/scripts/restart_flytlaunch.sh"])
# background_scripts[id] = True
#@rostful_server.app.route('/reboot')
#def reboot():
# id = str(uuid.uuid4())
# background_scripts[id] = False
# threading.Thread(target=lambda: run_script(id)).start()
# return redirect(url_for('flytConsole'))
@rostful_server.app.route('/reboot')
def reboot():
cmd = '/home/flytpod/FlytOS/src/flytOS/flyt_core/core_api/scripts/start_flytlaunch.sh'
os.execv(cmd,[cmd])
# p = subprocess.Popen(cmd, stdout = subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
# out,err = p.communicate()
return redirect(url_for('flytConsole'))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment