Class: Roby::Interface::Interface
- Inherits:
-
CommandLibrary
- Object
- CommandLibrary
- Roby::Interface::Interface
- Defined in:
- lib/roby/interface/interface.rb
Overview
The server-side implementation of the command-based interface
This exports all the services and/or APIs that are available through e.g. the Roby shell. It does not do any marshalling/demarshalling
Most methods can be accessed outside of the Roby execution thread. Methods that cannot will be noted in their documentation
About job management
One of the tasks of this class is to do job management. Jobs are the unit that is used to interact with a running Roby instance at a high level, as e.g. through a shell or a GUI. In Roby, jobs are represented by tasks that provide the Job task service and have a non-nil job ID. Up to two tasks can be associated with the job. The first is obviously the job task itself, i.e. the task that provides Job. Quite often, the job task will be a planning task (actually, one can see that Actions::Task provides Job). In this case, the planned task will be also associated with the job as its placeholder: while the job task represents the job’s deployment status, the placeholder task will represent the job’s execution status.
Defined Under Namespace
Classes: State
Instance Attribute Summary collapse
-
#cycle_end_listeners ⇒ #call
readonly
The blocks that listen to end-of-cycle notifications.
-
#job_listeners ⇒ #call
readonly
The blocks that listen to job notifications.
-
#job_notifications ⇒ Object
readonly
private
The set of pending job notifications for this cycle.
-
#tracked_jobs ⇒ Set<Integer>
readonly
private
The set of tracked jobs.
Attributes inherited from CommandLibrary
Instance Method Summary collapse
-
#actions ⇒ Array<Roby::Actions::Models::Action>
The set of actions available on CommandLibrary#app.
-
#drop_job(job_id) ⇒ Boolean
Drop a job.
-
#each_job_listener {|the| ... } ⇒ Object
Enumerates the job listeners currently registered through #on_job_notification.
-
#enable_backtrace_filtering(enable: true) ⇒ Object
Enable or disable backtrace filtering.
-
#find_job_by_id(id) ⇒ Roby::Task?
Finds a job task by its ID.
- #find_job_info_by_id(id) ⇒ Object
-
#find_job_placeholder_by_id(id) ⇒ Object
Finds the task that represents the given job ID.
-
#initialize(app) ⇒ Interface
constructor
Creates an interface from an existing Roby application.
-
#job_id_of_task(task) ⇒ Integer?
Returns the job ID of a task, where the task can either be a placeholder for the job or the job task itself.
-
#job_ids_of_task(task) ⇒ Array<Integer>
Returns all the job IDs of this task.
-
#job_notify(kind, job_id, job_name, *args) ⇒ Object
Dispatch the given job-related notification to all listeners.
- #job_state(task) ⇒ Object
-
#jobs ⇒ Hash<Integer,(Symbol,Roby::Task,Roby::Task)>
The jobs currently running on CommandLibrary#app‘s plan.
-
#kill_job(job_id) ⇒ Boolean
Kill a job.
-
#log_dir ⇒ Object
Returns the app’s log directory.
-
#log_server_port ⇒ Integer?
Returns the port of the log server.
-
#monitor_job(planning_task, task, new_task: false) ⇒ Object
Monitor the given task as a job.
-
#notify_cycle_end ⇒ Object
private
Notify the end-of-cycle to the listeners registered with #on_cycle_end.
-
#on_cycle_end(&block) {|the| ... } ⇒ Object
Add a handler called at each end of cycle.
-
#on_exception {|kind, error, tasks, job_ids| ... } ⇒ Object
Notification about plan exceptions.
-
#on_job_notification(&block) ⇒ Object
Registers a block to be called when a job changes state.
-
#on_notification {|source, level, message| ... } ⇒ Object
Registers a block to be called when a message needs to be dispatched from #notify.
-
#on_ui_event {|name, args| ... } ⇒ Object
Registers a block to be called when a message needs to be dispatched from #ui_event.
-
#push_pending_job_notifications ⇒ Object
private
Called in at_cycle_end to push job notifications.
-
#quit ⇒ Object
Requests for the Roby application to quit.
-
#reload_actions ⇒ Object
Reload the actions defined under the actions/ subfolder.
-
#reload_models ⇒ Object
Reload all models from this Roby application.
-
#reload_planners ⇒ Object
deprecated
Deprecated.
use #reload_actions instead
-
#remove_cycle_end(listener) ⇒ Object
Remove a handler that has been added to #on_cycle_end.
- #remove_exception_listener(listener) ⇒ Object
-
#remove_job_listener(listener) ⇒ Object
Remove a job listener added with #on_job_notification.
-
#remove_notification_listener(listener) ⇒ Object
Removes a notification listener added with #on_notification.
-
#remove_ui_event_listener(block) ⇒ Object
Removes a notification listener added with #on_ui_event.
-
#restart ⇒ Object
Requests for the Roby application to quit.
-
#start_job(m, arguments = Hash.new) ⇒ Integer
Starts a job.
Methods inherited from CommandLibrary
command, #commands, #each_subcommand, #execution_engine, #plan, subcommand, #subcommand
Constructor Details
#initialize(app) ⇒ Interface
Creates an interface from an existing Roby application
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/roby/interface/interface.rb', line 113 def initialize(app) super(app) app.plan.add_trigger Roby::Interface::Job do |task| if task.job_id && (planned_task = task.planned_task) monitor_job(task, planned_task, new_task: true) end end execution_engine.at_cycle_end do push_pending_job_notifications notify_cycle_end end @tracked_jobs = Set.new @job_notifications = Array.new @job_listeners = Array.new @job_monitoring_state = Hash.new @cycle_end_listeners = Array.new end |
Instance Attribute Details
#cycle_end_listeners ⇒ #call (readonly)
Returns the blocks that listen to end-of-cycle notifications. They are added with #on_cycle_end and removed with #remove_cycle_end.
98 99 100 |
# File 'lib/roby/interface/interface.rb', line 98 def cycle_end_listeners @cycle_end_listeners end |
#job_listeners ⇒ #call (readonly)
Returns the blocks that listen to job notifications. They are added with #on_job_notification and removed with #remove_job_listener.
93 94 95 |
# File 'lib/roby/interface/interface.rb', line 93 def job_listeners @job_listeners end |
#job_notifications ⇒ Object (readonly)
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
The set of pending job notifications for this cycle
108 109 110 |
# File 'lib/roby/interface/interface.rb', line 108 def job_notifications @job_notifications end |
#tracked_jobs ⇒ Set<Integer> (readonly)
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns the set of tracked jobs.
104 105 106 |
# File 'lib/roby/interface/interface.rb', line 104 def tracked_jobs @tracked_jobs end |
Instance Method Details
#actions ⇒ Array<Roby::Actions::Models::Action>
The set of actions available on CommandLibrary#app
150 151 152 153 154 155 156 157 158 |
# File 'lib/roby/interface/interface.rb', line 150 def actions result = [] app.planners.each do |planner_model| planner_model.each_registered_action do |_, act| result << act end end result end |
#drop_job(job_id) ⇒ Boolean
Drop a job
It removes the job from the list of missions but does not explicitely kill it
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 |
# File 'lib/roby/interface/interface.rb', line 202 def drop_job(job_id) return if !(task = find_job_by_id(job_id)) placeholder_task = task.planned_task if !placeholder_task plan.unmark_mission_task(task) return true end placeholder_task.remove_planning_task(task) if job_ids_of_task(placeholder_task).empty? plan.unmark_mission_task(placeholder_task) true else false end end |
#each_job_listener {|the| ... } ⇒ Object
Enumerates the job listeners currently registered through #on_job_notification
225 226 227 |
# File 'lib/roby/interface/interface.rb', line 225 def each_job_listener(&block) job_listeners.each(&block) end |
#enable_backtrace_filtering(enable: true) ⇒ Object
Enable or disable backtrace filtering
638 639 640 |
# File 'lib/roby/interface/interface.rb', line 638 def enable_backtrace_filtering(enable: true) app.filter_backtraces = enable end |
#find_job_by_id(id) ⇒ Roby::Task?
Finds a job task by its ID
518 519 520 521 522 |
# File 'lib/roby/interface/interface.rb', line 518 def find_job_by_id(id) execution_engine.execute do return plan.find_tasks(Job).with_arguments(job_id: id).to_a.first end end |
#find_job_info_by_id(id) ⇒ Object
505 506 507 508 509 510 511 512 |
# File 'lib/roby/interface/interface.rb', line 505 def find_job_info_by_id(id) execution_engine.execute do if planning_task = plan.find_tasks(Job).with_arguments(job_id: id).to_a.first task = planning_task.planned_task || planning_task return job_state(task), task, planning_task end end end |
#find_job_placeholder_by_id(id) ⇒ Object
Finds the task that represents the given job ID
It can be different than the job task when e.g. the job task is a planning task
528 529 530 531 532 |
# File 'lib/roby/interface/interface.rb', line 528 def find_job_placeholder_by_id(id) if task = find_job_by_id(id) return task.planned_task || task end end |
#job_id_of_task(task) ⇒ Integer?
Returns the job ID of a task, where the task can either be a placeholder for the job or the job task itself
358 359 360 |
# File 'lib/roby/interface/interface.rb', line 358 def job_id_of_task(task) job_ids_of_task(task).first end |
#job_ids_of_task(task) ⇒ Array<Integer>
Returns all the job IDs of this task
341 342 343 344 345 346 347 348 349 350 351 |
# File 'lib/roby/interface/interface.rb', line 341 def job_ids_of_task(task) if task.fullfills?(Job) [task.job_id] else task.each_planning_task.map do |planning_task| if planning_task.fullfills?(Job) planning_task.job_id end end.compact end end |
#job_notify(kind, job_id, job_name, *args) ⇒ Object
Dispatch the given job-related notification to all listeners
Listeners are registered with #on_job_notification
232 233 234 |
# File 'lib/roby/interface/interface.rb', line 232 def job_notify(kind, job_id, job_name, *args) job_notifications << [kind, job_id, job_name, args] end |
#job_state(task) ⇒ Object
456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 |
# File 'lib/roby/interface/interface.rb', line 456 def job_state(task) if !task.plan return JOB_FINALIZED elsif !plan.mission_task?(task) return JOB_DROPPED elsif task.success_event.emitted? return JOB_SUCCESS elsif task.failed_event.emitted? return JOB_FAILED elsif task.stop_event.emitted? return JOB_FINISHED elsif task.running? return JOB_STARTED elsif task.pending? if planner = task.planning_task if planner.success? return JOB_READY elsif planner.stop? return JOB_PLANNING_FAILED elsif planner.running? return JOB_PLANNING else return JOB_PLANNING_READY end else return JOB_READY end end end |
#jobs ⇒ Hash<Integer,(Symbol,Roby::Task,Roby::Task)>
The jobs currently running on CommandLibrary#app‘s plan
490 491 492 493 494 495 496 497 498 499 500 501 502 |
# File 'lib/roby/interface/interface.rb', line 490 def jobs result = Hash.new execution_engine.execute do planning_tasks = plan.find_tasks(Job).to_a planning_tasks.each do |job_task| job_id = job_task.job_id next if !job_id placeholder_job_task = job_task.planned_task || job_task result[job_id] = [job_state(placeholder_job_task), placeholder_job_task, job_task] end end result end |
#kill_job(job_id) ⇒ Boolean
Kill a job
It removes the job from the list of missions and kills the job’s main task
181 182 183 184 185 186 187 188 |
# File 'lib/roby/interface/interface.rb', line 181 def kill_job(job_id) if task = find_job_placeholder_by_id(job_id) plan.unmark_mission_task(task) task.stop! if task.running? true else false end end |
#log_dir ⇒ Object
Returns the app’s log directory
646 647 648 |
# File 'lib/roby/interface/interface.rb', line 646 def log_dir app.log_dir end |
#log_server_port ⇒ Integer?
Returns the port of the log server
141 142 143 |
# File 'lib/roby/interface/interface.rb', line 141 def log_server_port app.log_server_port end |
#monitor_job(planning_task, task, new_task: false) ⇒ Object
Monitor the given task as a job
It must be called within the Roby execution thread
365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 |
# File 'lib/roby/interface/interface.rb', line 365 def monitor_job(planning_task, task, new_task: false) # NOTE: this method MUST queue job notifications # UNCONDITIONALLY. Job tracking is done on a per-cycle basis (in # at_cycle_end) by {#push_pending_job_notifications} job_id = planning_task.job_id job_name = planning_task.job_name # This happens when a placeholder/planning pair is replaced by # another, but the job ID is inherited. We do this when e.g. # running an action that returns another planning pair if (state = @job_monitoring_state[job_id]) track_planning_state( state.job_id, state.job_name, state.service, planning_task) return end service = PlanService.new(task) @job_monitoring_state[job_id] = State.new(service, false, job_id, job_name) service.when_finalized do @job_monitoring_state.delete(job_id) end service.on_plan_status_change(initial: true) do |status| state = @job_monitoring_state[job_id] if !state.monitored? && (status == :mission) job_notify(JOB_MONITORED, job_id, job_name, service.task, service.task.planning_task) job_notify(job_state(service.task), job_id, job_name) state.monitored = true elsif state.monitored? && (status != :mission) job_notify(JOB_DROPPED, job_id, job_name) state.monitored = false end end track_planning_state(job_id, job_name, service, planning_task) service.on_replacement do |_current, new| if plan.mission_task?(new) && job_ids_of_task(new).include?(job_id) job_notify(JOB_REPLACED, job_id, job_name, new) job_notify(job_state(new), job_id, job_name) else job_notify(JOB_LOST, job_id, job_name, new) end end service.on(:start) do |ev| job_notify(JOB_STARTED, job_id, job_name) end service.on(:success) do |ev| job_notify(JOB_SUCCESS, job_id, job_name) end service.on(:failed) do |ev| job_notify(JOB_FAILED, job_id, job_name) end service.when_finalized do job_notify(JOB_FINALIZED, job_id, job_name) end end |
#notify_cycle_end ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Notify the end-of-cycle to the listeners registered with #on_cycle_end
606 607 608 609 610 |
# File 'lib/roby/interface/interface.rb', line 606 def notify_cycle_end cycle_end_listeners.each do |listener| listener.call end end |
#on_cycle_end(&block) {|the| ... } ⇒ Object
Add a handler called at each end of cycle
Interface-related objects that need to be notified must use this method instead of using ExecutionEngine#at_cycle_end on CommandLibrary#execution_engine, because the listener is guaranteed to be ordered properly w.r.t. #push_pending_job_notifications
595 596 597 598 599 600 |
# File 'lib/roby/interface/interface.rb', line 595 def on_cycle_end(&block) execution_engine.execute do cycle_end_listeners << block block end end |
#on_exception {|kind, error, tasks, job_ids| ... } ⇒ Object
Notification about plan exceptions
567 568 569 570 571 572 573 574 575 576 |
# File 'lib/roby/interface/interface.rb', line 567 def on_exception(&block) execution_engine.execute do execution_engine.on_exception(on_error: :raise) do |kind, exception, tasks| involved_job_ids = tasks.flat_map do |t| job_ids_of_task(t) if t.plan end.compact.to_set block.call(kind, exception, tasks, involved_job_ids) end end end |
#on_job_notification {|kind, job_id, job_name| ... } ⇒ Object #on_job_notification {|JOB_MONITORED, job_id, job_name, task, job_task| ... } ⇒ Object #on_job_notification {|JOB_REPLACED, job_id, job_name, task| ... } ⇒ Object
Registers a block to be called when a job changes state
All callbacks will be called with at minimum
322 323 324 325 |
# File 'lib/roby/interface/interface.rb', line 322 def on_job_notification(&block) job_listeners << block block end |
#on_notification {|source, level, message| ... } ⇒ Object
Registers a block to be called when a message needs to be dispatched from #notify
281 282 283 |
# File 'lib/roby/interface/interface.rb', line 281 def on_notification(&block) app.on_notification(&block) end |
#on_ui_event {|name, args| ... } ⇒ Object
Registers a block to be called when a message needs to be dispatched from #ui_event
271 272 273 |
# File 'lib/roby/interface/interface.rb', line 271 def on_ui_event(&block) app.on_ui_event(&block) end |
#push_pending_job_notifications ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Called in at_cycle_end to push job notifications
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 |
# File 'lib/roby/interface/interface.rb', line 239 def push_pending_job_notifications final_tracked_jobs = tracked_jobs.dup # Re-track jobs for which we have a recapture event job_notifications.each do |event, job_id, *| if event == JOB_MONITORED tracked_jobs << job_id final_tracked_jobs << job_id elsif event == JOB_DROPPED || event == JOB_LOST || event == JOB_FINALIZED final_tracked_jobs.delete(job_id) end end job_notifications = self.job_notifications.find_all do |event, job_id, *| if event == JOB_DROPPED !final_tracked_jobs.include?(job_id) else tracked_jobs.include?(job_id) end end self.job_notifications.clear each_job_listener do |listener| job_notifications.each do |kind, job_id, job_name, args| listener.call(kind, job_id, job_name, *args) end end @tracked_jobs = final_tracked_jobs end |
#quit ⇒ Object
Requests for the Roby application to quit
618 619 620 |
# File 'lib/roby/interface/interface.rb', line 618 def quit execution_engine.quit end |
#reload_actions ⇒ Object
Reload the actions defined under the actions/ subfolder
550 551 552 553 554 555 |
# File 'lib/roby/interface/interface.rb', line 550 def reload_actions execution_engine.execute do app.reload_actions end actions end |
#reload_models ⇒ Object
Reload all models from this Roby application
Do NOT do this while the robot does critical things
537 538 539 540 541 542 |
# File 'lib/roby/interface/interface.rb', line 537 def reload_models execution_engine.execute do app.reload_models end nil end |
#reload_planners ⇒ Object
use #reload_actions instead
545 546 547 |
# File 'lib/roby/interface/interface.rb', line 545 def reload_planners reload_actions end |
#remove_cycle_end(listener) ⇒ Object
Remove a handler that has been added to #on_cycle_end
613 614 615 |
# File 'lib/roby/interface/interface.rb', line 613 def remove_cycle_end(listener) cycle_end_listeners.delete(listener) end |
#remove_exception_listener(listener) ⇒ Object
579 580 581 582 583 |
# File 'lib/roby/interface/interface.rb', line 579 def remove_exception_listener(listener) execution_engine.execute do execution_engine.remove_exception_listener(listener) end end |
#remove_job_listener(listener) ⇒ Object
Remove a job listener added with #on_job_notification
331 332 333 |
# File 'lib/roby/interface/interface.rb', line 331 def remove_job_listener(listener) job_listeners.delete(listener) end |
#remove_notification_listener(listener) ⇒ Object
Removes a notification listener added with #on_notification
286 287 288 |
# File 'lib/roby/interface/interface.rb', line 286 def remove_notification_listener(listener) app.remove_notification_listener(listener) end |
#remove_ui_event_listener(block) ⇒ Object
Removes a notification listener added with #on_ui_event
276 277 278 |
# File 'lib/roby/interface/interface.rb', line 276 def remove_ui_event_listener(block) app.remove_ui_event_listener(block) end |
#restart ⇒ Object
Requests for the Roby application to quit
624 625 626 |
# File 'lib/roby/interface/interface.rb', line 624 def restart app.restart end |
#start_job(m, arguments = Hash.new) ⇒ Integer
Starts a job
164 165 166 167 168 169 |
# File 'lib/roby/interface/interface.rb', line 164 def start_job(m, arguments = Hash.new) execution_engine.execute do task, planning_task = app.prepare_action(m, mission: true, job_id: Job.allocate_job_id, **arguments) planning_task.job_id end end |