Class: Syskit::GUI::RuntimeState

Inherits:
Qt::Widget
  • Object
show all
Includes:
Roby::Hooks, Roby::Hooks::InstanceHooks
Defined in:
lib/syskit/gui/runtime_state.rb

Overview

UI that displays and allows to control jobs

Defined Under Namespace

Classes: ActionListDelegate, EventWidget

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(parent: nil, robot_name: 'default', syskit: Roby::Interface::Async::Interface.new, poll_period: 50) ⇒ RuntimeState

Returns a new instance of RuntimeState

Parameters:

  • syskit (Roby::Interface::Async::Interface)

    the underlying syskit interface

  • poll_period (Integer)

    how often should the syskit interface be polled (milliseconds). Set to nil if the polling is already done externally



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/syskit/gui/runtime_state.rb', line 115

def initialize(parent: nil, robot_name: 'default',
    syskit: Roby::Interface::Async::Interface.new, poll_period: 50)

    super(parent)

    @syskit = syskit
    @robot_name = robot_name
    reset

    @syskit_poll = Qt::Timer.new
    @syskit_poll_period = poll_period
    connect syskit_poll, SIGNAL('timeout()'),
        self, SLOT('poll_syskit_interface()')

    if poll_period
        @syskit_poll.start(poll_period)
    end

    create_ui

    @global_actions = Hash.new
    action = global_actions[:start]   = Qt::Action.new("Start", self)
    @starting_monitor = Qt::Timer.new
    connect @starting_monitor, SIGNAL('timeout()'),
        self, SLOT('monitor_syskit_startup()')
    connect action, SIGNAL('triggered()') do
        app_start(robot_name: @robot_name)
    end
    action = global_actions[:restart] = Qt::Action.new("Restart", self)
    connect action, SIGNAL('triggered()') do
        app_restart
    end
    action = global_actions[:quit]    = Qt::Action.new("Quit", self)
    connect action, SIGNAL('triggered()') do
        app_quit
    end

    @current_job = nil
    @current_orocos_tasks = Set.new
    @all_tasks = Set.new
    @known_loggers = nil
    @all_job_info = Hash.new
    syskit.on_ui_event do |event_name, *args|
        if w = @ui_event_widgets[event_name]
            w.show
            w.update(*args)
        else
            puts "don't know what to do with UI event #{event_name}, known events: #{@ui_event_widgets}"
        end
    end
    on_connection_state_changed do |state|
        @current_state = state
        connection_state.update_state state
    end
    syskit.on_reachable do
        @syskit_commands = syskit.client.syskit
        update_log_server_connection(syskit.log_server_port)
        @job_status_list.each_widget do |w|
            w.show_actions = true
        end
        action_combo.clear
        action_combo.enabled = true
        syskit.actions.sort_by(&:name).each do |action|
            next if action.advanced?
            action_combo.add_item(action.name, Qt::Variant.new(action.doc))
        end
        ui_logging_configuration.refresh
        global_actions[:start].visible = false
        global_actions[:restart].visible = true
        global_actions[:quit].visible = true
        @starting_monitor.stop
        run_hook :on_connection_state_changed, 'CONNECTED'
    end
    syskit.on_unreachable do
        @syskit_commands = nil
        @job_status_list.each_widget do |w|
            w.show_actions = false
        end
        @ui_event_widgets.each_value(&:hide)
        action_combo.enabled = false
        @batch_manager.cancel
        if remote_name == 'localhost'
            global_actions[:start].visible = true
        end
        ui_logging_configuration.refresh
        global_actions[:restart].visible = false
        global_actions[:quit].visible = false
        if @current_state != 'RESTARTING'
            run_hook :on_connection_state_changed, 'UNREACHABLE'
        end
    end
    syskit.on_job do |job|
        job.start
        monitor_job(job)
    end
end

Instance Attribute Details

#action_comboObject (readonly)

The combo box used to create new jobs



36
37
38
# File 'lib/syskit/gui/runtime_state.rb', line 36

def action_combo
  @action_combo
end

#all_job_infoObject (readonly)

Job information for tasks in the rebuilt plan



46
47
48
# File 'lib/syskit/gui/runtime_state.rb', line 46

def all_job_info
  @all_job_info
end

#all_tasksObject (readonly)

All known tasks



44
45
46
# File 'lib/syskit/gui/runtime_state.rb', line 44

def all_tasks
  @all_tasks
end

#connection_stateObject (readonly)

The connection state, which gives access to the global Syskit state



41
42
43
# File 'lib/syskit/gui/runtime_state.rb', line 41

def connection_state
  @connection_state
end

#current_jobObject (readonly)

The job that is currently selected



38
39
40
# File 'lib/syskit/gui/runtime_state.rb', line 38

def current_job
  @current_job
end

#current_orocos_tasksObject (readonly)

The list of task names of the task currently displayed by the task inspector



56
57
58
# File 'lib/syskit/gui/runtime_state.rb', line 56

def current_orocos_tasks
  @current_orocos_tasks
end

#current_stateObject (readonly)

The current connection state



65
66
67
# File 'lib/syskit/gui/runtime_state.rb', line 65

def current_state
  @current_state
end

#global_actionsArray<Qt::Action> (readonly)

Returns a list of actions that can be performed on the Roby instance

Returns:

  • (Array<Qt::Action>)


62
63
64
# File 'lib/syskit/gui/runtime_state.rb', line 62

def global_actions
  @global_actions
end

#job_expanded_statusObject (readonly)

The [ExpandedJobStatus] widget in which we display expanded job information



34
35
36
# File 'lib/syskit/gui/runtime_state.rb', line 34

def job_expanded_status
  @job_expanded_status
end

#job_status_listObject (readonly)

The [WidgetList] widget in which we display the summary of job status



31
32
33
# File 'lib/syskit/gui/runtime_state.rb', line 31

def job_status_list
  @job_status_list
end

#main_layoutObject (readonly)

The toplevel layout



26
27
28
# File 'lib/syskit/gui/runtime_state.rb', line 26

def main_layout
  @main_layout
end

#name_serviceObject (readonly)

The name service which allows us to resolve Rock task contexts



49
50
51
# File 'lib/syskit/gui/runtime_state.rb', line 49

def name_service
  @name_service
end

#new_job_layoutObject (readonly)

The layout used to organize the widgets to create new jobs



28
29
30
# File 'lib/syskit/gui/runtime_state.rb', line 28

def new_job_layout
  @new_job_layout
end

#syskitRoby::Interface::Async::Interface (readonly)

Returns the underlying syskit interface

Returns:

  • (Roby::Interface::Async::Interface)

    the underlying syskit interface



21
22
23
# File 'lib/syskit/gui/runtime_state.rb', line 21

def syskit
  @syskit
end

#syskit_log_streamObject (readonly)

An async object to access the log stream



23
24
25
# File 'lib/syskit/gui/runtime_state.rb', line 23

def syskit_log_stream
  @syskit_log_stream
end

#syskit_pollObject (readonly)

Returns the value of attribute syskit_poll



553
554
555
# File 'lib/syskit/gui/runtime_state.rb', line 553

def syskit_poll
  @syskit_poll
end

#ui_logging_configurationObject (readonly)

A logging configuration widget we use to manage logging



53
54
55
# File 'lib/syskit/gui/runtime_state.rb', line 53

def ui_logging_configuration
  @ui_logging_configuration
end

#ui_task_inspectorObject (readonly)

A task inspector widget we use to display the task states



51
52
53
# File 'lib/syskit/gui/runtime_state.rb', line 51

def ui_task_inspector
  @ui_task_inspector
end

Instance Method Details

#app_quitObject



294
295
296
# File 'lib/syskit/gui/runtime_state.rb', line 294

def app_quit
    syskit.quit
end

#app_restartObject



298
299
300
301
302
303
304
# File 'lib/syskit/gui/runtime_state.rb', line 298

def app_restart
    run_hook :on_connection_state_changed, 'RESTARTING'
    if @syskit_pid
        @starting_monitor.start(100)
    end
    syskit.restart
end

#app_start(robot_name: 'default') ⇒ Object



273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
# File 'lib/syskit/gui/runtime_state.rb', line 273

def app_start(robot_name: 'default')
    robot_name, start_controller = AppStartDialog.exec(Roby.app.robots.names, self, default_robot_name: robot_name)
    return unless robot_name

    extra_args = Array.new
    if !robot_name.empty?
        extra_args << "-r" << robot_name
    end
    if start_controller
        extra_args << "-c"
    end
    extra_args.concat(
        Roby.app.argv_set.flat_map { |arg| ['--set', arg] })
    @syskit_pid =
        Kernel.spawn Gem.ruby, '-S', 'syskit', 'run', "--wait-shell-connection",
            *extra_args,
            pgroup: true
    @starting_monitor.start(100)
    run_hook :on_connection_state_changed, 'STARTING'
end

#create_uiObject



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
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
# File 'lib/syskit/gui/runtime_state.rb', line 387

def create_ui
    job_summary = Qt::Widget.new
    job_summary_layout = Qt::VBoxLayout.new(job_summary)
    job_summary_layout.add_layout(@new_job_layout  = create_ui_new_job)

    @connection_state = GlobalStateLabel.new(name: remote_name)
    on_progress do |message|
        state = connection_state.current_state.to_s
        connection_state.update_text("%s - %s" % [state, message])
    end
    job_summary_layout.add_widget(connection_state, 0)

    @clear_button = Qt::PushButton.new("Clear Finished Jobs")
    job_summary_layout.add_widget(@clear_button)
    @clear_button.connect(SIGNAL(:clicked)) do
        @job_status_list.clear_widgets do |w|
            if !w.job.active?
                w.job.stop
                true
            end
        end
    end

    @batch_manager = BatchManager.new(@syskit, self)
    job_summary_layout.add_widget(@batch_manager)
    @batch_manager.connect(SIGNAL('active(bool)')) do |active|
        if active then @batch_manager.show
        else @batch_manager.hide
        end
    end
    @batch_manager.hide
    connection_state.connect(SIGNAL('clicked(QPoint)')) do
        deselect_job
    end

    @job_status_list = WidgetList.new(self)
    job_status_scroll = Qt::ScrollArea.new
    job_status_scroll.widget = @job_status_list
    job_summary_layout.add_widget(job_status_scroll, 1)
    @main_layout = Qt::VBoxLayout.new(self)

    @ui_event_widgets = create_ui_event_widgets
    @ui_event_widgets.each_value do |w|
        @main_layout.add_widget(w.widget)
    end

    splitter = Qt::Splitter.new
    splitter.add_widget job_summary
    splitter.add_widget(@job_expanded_status = ExpandedJobStatus.new)
    connect(@job_expanded_status, SIGNAL('fileOpenClicked(const QUrl&)'),
        self, SIGNAL('fileOpenClicked(const QUrl&)'))

    task_inspector_widget = Qt::Widget.new
    task_inspector_layout = Qt::VBoxLayout.new(task_inspector_widget)
    task_inspector_layout.add_widget(
        @ui_hide_loggers = Qt::CheckBox.new("Show loggers"))
    task_inspector_layout.add_widget(
        @ui_task_inspector = Vizkit.default_loader.TaskInspector)
    @ui_hide_loggers.checked = false
    @ui_hide_loggers.connect SIGNAL('toggled(bool)') do |checked|
        @known_loggers = nil
        update_tasks_info
    end

    @ui_logging_configuration = LoggingConfiguration.new(syskit)

    management_tab_widget = Qt::TabWidget.new(self)
    management_tab_widget.addTab(task_inspector_widget, "Tasks")
    management_tab_widget.addTab(ui_logging_configuration, "Logging")

    splitter.add_widget(management_tab_widget)
    job_expanded_status.set_size_policy(Qt::SizePolicy::MinimumExpanding, Qt::SizePolicy::MinimumExpanding)
    @main_layout.add_widget splitter, 1
    w = splitter.size.width
    splitter.sizes = [Integer(w * 0.25), Integer(w * 0.50), Integer(w * 0.25)]
end

#create_ui_event_button(text) ⇒ Object



471
472
473
474
475
# File 'lib/syskit/gui/runtime_state.rb', line 471

def create_ui_event_button(text)
    button = Qt::PushButton.new(text)
    button.flat = true
    button
end

#create_ui_event_frameObject



464
465
466
467
468
469
# File 'lib/syskit/gui/runtime_state.rb', line 464

def create_ui_event_frame
    frame = Qt::Frame.new(self)
    frame.frame_shape = Qt::Frame::StyledPanel
    frame.setStyleSheet("QFrame { background-color: rgb(205,235,255); border-radius: 2px; }")
    frame
end

#create_ui_event_orogen_config_changedObject



477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
# File 'lib/syskit/gui/runtime_state.rb', line 477

def create_ui_event_orogen_config_changed
    syskit_orogen_config_changed = create_ui_event_frame
    layout = Qt::HBoxLayout.new(syskit_orogen_config_changed)
    layout.add_widget(Qt::Label.new("oroGen configuration files changes on disk"), 1)
    layout.add_widget(reload = create_ui_event_button("Reload"))
    layout.add_widget(close  = create_ui_event_button("Close"))
    reload.connect(SIGNAL('clicked()')) do
        @syskit_commands.async_reload_config do
            syskit_orogen_config_changed.hide
        end
    end
    close.connect(SIGNAL('clicked()')) do
        syskit_orogen_config_changed.hide
    end
    EventWidget.new('syskit_orogen_config_changed', syskit_orogen_config_changed, lambda { })
end

#create_ui_event_orogen_config_reloadedObject



494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
# File 'lib/syskit/gui/runtime_state.rb', line 494

def create_ui_event_orogen_config_reloaded
    syskit_orogen_config_reloaded = create_ui_event_frame
    layout = Qt::HBoxLayout.new(syskit_orogen_config_reloaded)
    layout.add_widget(label = Qt::Label.new(), 1)
    layout.add_widget(apply = create_ui_event_button("Reconfigure"))
    layout.add_widget(close = create_ui_event_button("Close"))
    apply.connect(SIGNAL('clicked()')) do
        @syskit_commands.async_redeploy do
            syskit_orogen_config_reloaded.hide
        end
    end
    close.connect(SIGNAL('clicked()')) do
        syskit_orogen_config_reloaded.hide
    end
    syskit_orogen_config_reloaded_hook = lambda do |changed_tasks, changed_tasks_running|
        if changed_tasks.empty?
            label.text = "oroGen configuration updated"
            apply.hide
        elsif changed_tasks_running.empty?
            label.text = "oroGen configuration modifications applied to #{changed_tasks.size} configured but not running tasks"
            apply.hide
        else
            label.text = "oroGen configuration modifications applied to #{changed_tasks_running.size} running tasks and #{changed_tasks.size - changed_tasks_running.size} configured but not running tasks"
            apply.show
        end
    end
    EventWidget.new('syskit_orogen_config_reloaded',
                    syskit_orogen_config_reloaded,
                    syskit_orogen_config_reloaded_hook)
end

#create_ui_event_widgetsObject



525
526
527
528
529
530
531
532
533
534
535
536
# File 'lib/syskit/gui/runtime_state.rb', line 525

def create_ui_event_widgets
    widgets = [
        create_ui_event_orogen_config_reloaded,
        create_ui_event_orogen_config_changed
    ]
    ui_event_widgets = Hash.new
    widgets.each do |w|
        w.hide
        ui_event_widgets[w.name] = w
    end
    ui_event_widgets
end

#create_ui_new_jobObject



538
539
540
541
542
543
544
545
546
547
548
549
550
551
# File 'lib/syskit/gui/runtime_state.rb', line 538

def create_ui_new_job
    new_job_layout = Qt::HBoxLayout.new
    label   = Qt::Label.new("New Job", self)
    label.set_size_policy(Qt::SizePolicy::Minimum, Qt::SizePolicy::Minimum)
    @action_combo = Qt::ComboBox.new(self)
    action_combo.enabled = false
    action_combo.item_delegate = ActionListDelegate.new(self)
    new_job_layout.add_widget label
    new_job_layout.add_widget action_combo, 1
    action_combo.connect(SIGNAL('activated(QString)')) do |action_name|
        @batch_manager.create_new_job(action_name)
    end
    new_job_layout
end

#deselect_jobObject



583
584
585
586
587
588
589
590
591
592
593
# File 'lib/syskit/gui/runtime_state.rb', line 583

def deselect_job
    @current_job = nil
    job_expanded_status.deselect
    all_tasks.clear
    @known_loggers = nil
    all_job_info.clear
    if syskit_log_stream
        update_tasks_info
    end
    job_expanded_status.add_tasks_info(all_tasks, all_job_info)
end

#hide_loggers?Boolean

Returns:

  • (Boolean)


265
266
267
# File 'lib/syskit/gui/runtime_state.rb', line 265

def hide_loggers?
    !@ui_hide_loggers.checked?
end

#logger_task?(t) ⇒ Boolean

Returns:

  • (Boolean)


306
307
308
309
310
311
# File 'lib/syskit/gui/runtime_state.rb', line 306

def logger_task?(t)
    return if @logger_m == false
    @logger_m ||= Syskit::TaskContext.
        find_model_from_orogen_name('logger::Logger') || false
    t.kind_of?(@logger_m)
end

#monitor_job(job) ⇒ 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.

Create the UI elements for the given job

Parameters:

  • job (Roby::Interface::Async::JobMonitor)


575
576
577
578
579
580
581
# File 'lib/syskit/gui/runtime_state.rb', line 575

def monitor_job(job)
    job_status = JobStatusDisplay.new(job, @batch_manager)
    job_status_list.add_widget job_status
    job_status.connect(SIGNAL('clicked()')) do
        select_job(job_status)
    end
end

#monitor_syskit_startupObject



212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/syskit/gui/runtime_state.rb', line 212

def monitor_syskit_startup
    return unless @syskit_pid

    begin
        _pid, has_quit = Process.waitpid2(
            @syskit_pid, Process::WNOHANG)
    rescue Errno::ECHILD
        has_quit = true
    end

    if has_quit
        @syskit_pid = nil
        run_hook :on_connection_state_changed, 'UNREACHABLE'
        @starting_monitor.stop
    end
end

#poll_syskit_interfaceObject

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.

Sets up polling on a given syskit interface



558
559
560
561
562
563
564
565
566
567
# File 'lib/syskit/gui/runtime_state.rb', line 558

def poll_syskit_interface
    syskit.poll
    if syskit_log_stream
        if syskit_log_stream.poll(max: 0.05) == Roby::Interface::Async::Log::STATE_PENDING_DATA
            syskit_poll.interval = 0
        else
            syskit_poll.interval = @syskit_poll_period
        end
    end
end

#remote_nameObject



269
270
271
# File 'lib/syskit/gui/runtime_state.rb', line 269

def remote_name
    syskit.remote_name
end

#resetObject



230
231
232
233
234
235
# File 'lib/syskit/gui/runtime_state.rb', line 230

def reset
    Orocos.initialize
    @logger_m = nil
    orocos_corba_nameservice = Orocos::CORBA::NameService.new(syskit.remote_name)
    @name_service = Orocos::Async::NameService.new(orocos_corba_nameservice)
end

#select_job(job_status) ⇒ Object



595
596
597
598
599
600
601
602
603
# File 'lib/syskit/gui/runtime_state.rb', line 595

def select_job(job_status)
    @current_job = job_status.job
    all_tasks.clear
    @known_loggers = nil
    all_job_info.clear
    update_tasks_info
    job_expanded_status.select(job_status)
    job_expanded_status.add_tasks_info(all_tasks, all_job_info)
end

#update_log_server_connection(port) ⇒ Object



237
238
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
# File 'lib/syskit/gui/runtime_state.rb', line 237

def update_log_server_connection(port)
    if syskit_log_stream && (syskit_log_stream.port == port)
        return
    elsif syskit_log_stream
        syskit_log_stream.close
    end
    @syskit_log_stream = Roby::Interface::Async::Log.new(syskit.remote_name, port: port)
    syskit_log_stream.on_reachable do
        deselect_job
    end
    syskit_log_stream.on_init_progress do |rx, expected|
        run_hook :on_progress, ("loading %02i" % [Float(rx) / expected * 100])
    end
    syskit_log_stream.on_update do |cycle_index, cycle_time|
        if syskit_log_stream.init_done?
            time_s = "#{cycle_time.strftime('%H:%M:%S.%3N')}"
            run_hook :on_progress, ("@%i %s" % [cycle_index, time_s])

            job_expanded_status.update_time(cycle_index, cycle_time)
            update_tasks_info
            job_expanded_status.add_tasks_info(all_tasks, all_job_info)
            job_expanded_status.scheduler_state = syskit_log_stream.scheduler_state
            job_expanded_status.update_chronicle
        end
        syskit_log_stream.clear_integrated
    end
end

#update_orocos_tasksObject



360
361
362
363
364
365
366
367
368
369
370
371
372
373
# File 'lib/syskit/gui/runtime_state.rb', line 360

def update_orocos_tasks
    candidate_tasks = self.all_tasks.
        find_all { |t| t.kind_of?(Syskit::TaskContext) }
    orocos_tasks = candidate_tasks.map { |t| t.arguments[:orocos_name] }.compact.to_set
    removed = current_orocos_tasks - orocos_tasks
    new     = orocos_tasks - current_orocos_tasks
    removed.each do |task_name|
        ui_task_inspector.remove_task(task_name)
    end
    new.each do |task_name|
        ui_task_inspector.add_task(name_service.proxy(task_name))
    end
    @current_orocos_tasks = orocos_tasks
end

#update_tasks_infoObject



313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
# File 'lib/syskit/gui/runtime_state.rb', line 313

def update_tasks_info
    if current_job
        job_task = syskit_log_stream.plan.find_tasks(Roby::Interface::Job).
            with_arguments(job_id: current_job.job_id).
            first
        return if !job_task
        placeholder_task = job_task.planned_task
        return if !placeholder_task

        dependency = placeholder_task.relation_graph_for(Roby::TaskStructure::Dependency)
        tasks = dependency.enum_for(:depth_first_visit, placeholder_task).to_a
        tasks << job_task
    else
        tasks = syskit_log_stream.plan.tasks
    end

    if hide_loggers?
        if !@known_loggers
            @known_loggers = Set.new
            all_tasks.delete_if do |t|
                @known_loggers << t if logger_task?(t)
            end
        end

        tasks = tasks.find_all do |t|
            if all_tasks.include?(t)
                true
            elsif @known_loggers.include?(t)
                false
            elsif logger_task?(t)
                @known_loggers << t
                false
            else true
            end
        end
    end
    all_tasks.merge(tasks)
    tasks.each do |job|
        if job.kind_of?(Roby::Interface::Job)
            if placeholder_task = job.planned_task
                all_job_info[placeholder_task] = job
            end
        end
    end
    update_orocos_tasks
end