4 from __future__
import print_function, division
15 from threading
import Lock
21 from actionlib_msgs.msg
import GoalStatus
22 from turtlesim.msg
import Pose
23 from turtle_actionlib.msg
import ShapeAction, ShapeGoal
26 import plotly.graph_objs
as go
29 import dash_core_components
as dcc
30 import dash_html_components
as html
32 from flask
import jsonify
37 GOAL_STATUS_TO_TXT = { getattr(GoalStatus, x): x
for x
in dir(GoalStatus)
if x.isupper() }
44 assets_folder=os.path.join(rospkg.RosPack().get_path(
'turtlesim_dash_tutorial'),
'dash_assets'),
45 external_stylesheets=[
47 'href':
'https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css',
49 'integrity':
'sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T',
50 'crossorigin':
'anonymous',
58 Create a Flask server to display the UI and a ROS node to send commands to 65 APP_STATUS_URL =
'/ros_api/status' 66 APP_STATUS_ENDPOINT =
'ros_status' 71 TURTLE_SHAPE_ACTION_NAME =
'turtle_shape' 72 TURTLE_POSE_TOPIC =
'/turtle1/pose' 77 POSE_UPDATE_INTERVAL = 5
78 POSE_MAX_TIMESTEPS = 2000
79 POSE_ATTRIBUTES = [
'x',
'y',
'theta',
'linear_velocity',
'angular_velocity']
82 SERVER_STATUS_OUTPUT_FORMAT =
"Shape Server Status: {status}" 92 signal.signal(signal.SIGINT, self.
stop)
97 (1+len(Dashboard.POSE_ATTRIBUTES), Dashboard.POSE_MAX_TIMESTEPS)) * np.nan
113 rospy.loginfo(
"Connecting to turtle_shape...")
115 rospy.loginfo(
"...turtle_shape connected.")
116 self.
_app.run_server(host=Dashboard.APP_HOST,
117 port=Dashboard.APP_PORT,
120 def stop(self, *args, **kwargs):
122 print(
"Shutting down Dash server")
128 Define the app layout and callbacks here 134 pose_graph_layout = html.Div(dcc.Graph(id=
'pose', style={
'width':
'100%' }), className=
'row')
138 shape_params_layout = html.Div(
140 dcc.Input(id=
"shape-edges", type=
'number', placeholder=
'Num Edges', className=
'col mx-2'),
141 dcc.Input(id=
"shape-radius", type=
'number', placeholder=
'Radius', className=
'col mx-2'),
142 html.Button(
"Trace Shape", id=
'trace-button', n_clicks=0, className=
'btn btn-large btn-primary col-3'),
148 server_status_layout = html.Div(
149 dcc.Markdown(id=
'server-status', className=
'col'),
154 self.
_app.layout = html.Div(
157 html.Button(id=
'refresh-status', n_clicks=0, style={
'display':
'none' }),
160 html.Div(html.H3(
'Shape Tracing:', className=
'col'), className=
'row mt-4'),
162 server_status_layout,
165 html.Div(html.H3(
'Pose History:', className=
'col'), className=
'row my-2'),
169 dcc.Interval(id=
'interval-component',
171 interval=(Dashboard.POSE_UPDATE_INTERVAL * 1000)),
173 className=
"container" 178 dash.dependencies.Output(
'pose',
'figure'),
179 [dash.dependencies.Input(
'interval-component',
'n_intervals')]
185 dash.dependencies.Output(
'trace-button',
'autoFocus'),
186 [dash.dependencies.Input(
'trace-button',
'n_clicks')],
187 [dash.dependencies.State(
'shape-edges',
'value'),
188 dash.dependencies.State(
'shape-radius',
'value')]
193 dash.dependencies.Output(
'server-status',
'children'),
194 [dash.dependencies.Input(
'refresh-status',
'n_clicks')]
199 Dashboard.APP_STATUS_URL,
200 Dashboard.APP_STATUS_ENDPOINT,
206 Define a callback to populate the server status display when the status 207 refresh button (hidden) is pressed 209 def server_status_callback(n_clicks):
211 return Dashboard.SERVER_STATUS_OUTPUT_FORMAT.format(**locals())
213 return server_status_callback
217 Define a callback that will be invoked every time the 'Trace' button is 220 def trace_shape_callback(n_clicks, num_edges, radius):
222 if n_clicks
is None or n_clicks == 0:
227 num_edges = int(num_edges)
228 radius = float(radius)
229 except Exception
as e:
230 rospy.logerr(
"Error parsing params - {}\n{}".format(e, traceback.format_exc()))
234 goal = ShapeGoal(edges=num_edges, radius=radius)
244 rospy.loginfo(
"ShapeServer: Interior Angle - {result.interior_angle}, Apothem - {result.apothem}".format(**locals()))
248 return trace_shape_callback
252 Define a callback that will be invoked on every update of the interval 253 component. Keep in mind that we return a callback here; not a result 255 def pose_history_callback(n_intervals):
263 x=pose_history[0, :],
264 y=pose_history[idx+1, :],
267 for idx, attr
in enumerate(Dashboard.POSE_ATTRIBUTES)
280 return {
'data': data,
'layout': layout }
282 return pose_history_callback
286 The callback for the position of the turtle on 287 :const:`TURTLE_POSE_TOPIC` 295 rospy.Time.now().to_time() % 1000,
300 msg.angular_velocity,
def stop(self, args, kwargs)
def _define_pose_history_callback(self)
def _define_trace_shape_callback(self)
def _define_server_status_callback(self)
def _flask_status_endpoint(self)