Created
August 25, 2016 05:14
-
-
Save asmodehn/41bccf3949fd7bf2d4e710e497e2ddc9 to your computer and use it in GitHub Desktop.
Rostful + rosbridge from @dhirajdhule
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 / | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- 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