sdlHandler.py 17.7 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# pylint: disable=C0302
"""
    TASTE GUI - SDL Statechart handler

    Connect to OpenGEODE and display the statechart during the simulation

    Copyright (c) 2012-2015 European Space Agency

    Designed and implemented by Maxime Perrotin

    Contact: maxime.perrotin@esa.int
"""

import os
Maxime Perrotin's avatar
Maxime Perrotin committed
18
import ctypes
Maxime Perrotin's avatar
Maxime Perrotin committed
19
from functools import partial
20

21
from PySide.QtGui import (QDockWidget, QPushButton, QGridLayout, QListWidget,
22
                          QUndoStack, QUndoCommand, QToolButton)
23
from PySide.QtCore import QObject, Signal, Slot, Qt, QFile
24
from PySide.QtUiTools import QUiLoader
25

26
27
import asn1_value_editor
from standalone_editor import asn1sccToasn1ValueEditorTypes
Maxime Perrotin's avatar
Maxime Perrotin committed
28
import vn
29
import resources
30
31
32
33
34
try:
    import opengeode
except ImportError:
    print 'OpenGEODE module is not available'

35
36
import opengeode.undoCommands as undo

Maxime Perrotin's avatar
Maxime Perrotin committed
37
38
39
40
41
try:
    import dataview_uniq_asn as ASN1
except ImportError:
    print 'No Python A mapper generated dataview, SDL handler cannot be used'
    ASN1 = None
42

Maxime Perrotin's avatar
Maxime Perrotin committed
43

44
45
class SendTC(QUndoCommand):
    ''' Undo command to send a message to the running system '''
46
47
    def __init__(self, handler, old_state):
        ''' Init: save the current and old states '''
48
49
        super(SendTC, self).__init__()
        self.handler = handler
50
51
52
        self.new_state = handler.current_hash
        self.old_state = old_state

53
54
55
    def undo(self):
        ''' Undo a send TC: Restore the system state as it was before the TC
        was sent - setting back all global variables and internal state '''
56
        self.handler.restore_global_state(self.old_state)
57
58

    def redo(self):
59
60
61
        ''' Set the internal variables to the new state '''
        if self.new_state != self.handler.current_hash:
            self.handler.restore_global_state(self.new_state)
62
63


64
class sdlHandler(QObject):
65
66
67
    '''
        Class managing SDL models
    '''
68
69
70
71
72
73
74
    # Qt signals:
    #    - msc is used to display an item on the MSC diagram
    #    - msc_macro_start/top are used to control the undo stack macros
    #    - allowed_messages is used to activate/deactivate GUI buttons
    #      depending on the context, driven by the SDL model internal state
    #    - msc undo/redo are used to request an undo stack action to the MSC
    #      handler, which lives in a different thread.
75
    msc = Signal(unicode, unicode, QUndoStack)
76
77
78
79
    msc_macro_start = Signal()
    msc_macro_stop = Signal()
    msc_undo = Signal()
    msc_redo = Signal()
80
    allowed_messages = Signal(list)
81

82
83
84
    def __init__(self, parent):
        ''' Startup: check if there are some SDL files in
            the directory and try to parse them and build the widget '''
85
        super(sdlHandler, self).__init__()
86
        self.parent = parent
87
88
89
90
91
92
93
94
95
96
        all_files = os.listdir('.')
        pr_files = []
        for each in all_files:
            if each.endswith('.pr'):
                pr_files.append(each)
        if not pr_files:
            raise IOError('SDL Handler failed to initialize')
        self.ast = opengeode.parse(pr_files)
        try:
            root_ast = self.ast[0]
Maxime Perrotin's avatar
Maxime Perrotin committed
97
            self.proc = root_ast.processes[0]
98
99
        except IndexError:
            raise IOError('SDL Handler failed to initialize')
Maxime Perrotin's avatar
Maxime Perrotin committed
100
101
        opengeode.Helper.flatten(self.proc)
        graph = opengeode.Statechart.create_dot_graph(self.proc)
102
103
104
105
        self.sdl_scene = opengeode.SDL_Scene('statechart')
        self.sdl_view = opengeode.SDL_View(self.sdl_scene)
        opengeode.Statechart.render_statechart(self.sdl_scene, graph)
        self.sdl_view.refresh()
106
107
108
109
110
111
        # Pointer to the shared library, set by gui.py
        self._dll = None
        self.dock = None
        self.dock_state = None
        self.asn1_editor = None
        self.tree_items = {}
112
        self.dock_simu = self.start_simu()
113
        self.current_sdl_state = None
114
115
        # Handle the state of all timers ({'timerName': 'set'/'unset'})
        self.timers = {}
116
        # Keep a state graph to allow undo/redo and state exploration
117
        self.set_of_states = {}
118
        self.current_hash = None
119
120
        # Create a stack for handling undo/redo commands
        self.undo_stack = QUndoStack(self)
121
122
123
124
125
126
        # Associate the Undo, Redo, Reset buttons with an action
        widget = self.dock_simu.widget()
        undo_button = widget.findChild(QToolButton, 'undoButton')
        redo_button = widget.findChild(QToolButton, 'redoButton')
        reset_button = widget.findChild(QToolButton, 'resetButton')
        reset_button.clicked.connect(self.reset_simulation)
Maxime Perrotin's avatar
Maxime Perrotin committed
127
128
        undo_button.clicked.connect(self.undo)
        redo_button.clicked.connect(self.redo)
129
130
131
132
133
134
135
136
137
138

    @property
    def dll(self):
        return self._dll

    @dll.setter
    def dll(self, value):
        ''' Set the DLL - initialize the docks '''
        self._dll = value
        self.dock = QDockWidget('SDL/Statechart Viewer', self.parent)
139
        self.dock.setFloating(True)
Maxime Perrotin's avatar
Maxime Perrotin committed
140
        self.dock.resize(400, 400)
141
        self.dock.setObjectName('SDLViewer')
142
        self.parent.addDockWidget(Qt.RightDockWidgetArea, self.dock)
143
144
145
146
147
        self.dock.setAllowedAreas(Qt.NoDockWidgetArea)
        self.sdl_view.show()
        self.dock.setWidget(self.sdl_view)
        self.running = False
        self.dock.hide()
148
149
        # Dock widget to display the internal state
        self.asn1_editor = asn1_value_editor.asn1Editor(self.parent)
150
        self.asn1_editor.hideExtraColumns()
151
        self.dock_state = QDockWidget('Internal state', self.parent)
152
153
        self.dock_state.setFloating(False)
        #self.dock_state.resize(400, 400)
154
155
        self.dock_state.setObjectName('InternalStateViewer')
        self.parent.addDockWidget(Qt.RightDockWidgetArea, self.dock_state)
156
        #self.dock_state.setAllowedAreas(Qt.NoDockWidgetArea)
157
        self.dock_state.setWidget(self.asn1_editor)
158
159
        self.dock_state.show()
        self.parent.tabifyDockWidget(self.dock_state, self.dock_simu)
160
161
162
163
164
165
166
        # Add the SDL variables to the ASN.1 editor
        row = 0
        for var, (sort, _) in self.proc.variables.viewitems():
            dataview = self.proc.dataview
            item = asn1sccToasn1ValueEditorTypes(dataview, var, sort)
            self.tree_items[var] = self.asn1_editor.setAsn1Model(item, row)
            row += 1
167
168
        # In the simulation panel, set the buttons to send paramless TC/timers
        self.set_paramless_tc()
Maxime Perrotin's avatar
Maxime Perrotin committed
169
        self.set_paramless_tm()
170
171
172
173
174
        self.get_sdl_state = getattr(value,
                                     '{}_state'.format(self.proc.processName))
        self.get_sdl_state.restype = ctypes.c_char_p
        # Initialization: set current state and internal variables
        self.current_sdl_state = None
175
        self.check_state()
176
        self.init_state = self.current_hash = self.on_event()
177
        self.init_timers()
178
179
180
181
182
183
184
185
186

    @Slot()
    def startStop(self):
        ''' Trigger or stop the SDL display '''
        self.running = not self.running
        if self.running:
            self.dock.show()
        else:
            self.dock.hide()
187

188
    def check_state(self):
189
        ''' Highlight the current state on the statechart diagram '''
190
191
192
193
194
195
        state = self.get_sdl_state()
        if state != self.current_sdl_state:
            self.current_sdl_state = state
            self.sdl_scene.clear_highlight()
            for each in self.sdl_scene.find_text(u'\\b{}\\b'.format(state)):
                self.sdl_scene.highlight(each)
Maxime Perrotin's avatar
Maxime Perrotin committed
196
            self.add_to_msc('condition', state)
197
            self.log_area.addItem('New state: {}'.format(state))
198

199
200
201
202
203
    def reset_simulation(self):
        ''' Jump to the first step of simulation '''
        self.restore_global_state(self.init_state)
        # TODO: clean log, clean MSC, rewind undo stack

Maxime Perrotin's avatar
Maxime Perrotin committed
204
205
206
207
208
    def undo(self):
        ''' Called when the undo button is pressed '''
        self.undo_stack.undo()
        self.check_state()
        self.current_hash = self.on_event()
209
        self.msc_undo.emit()
Maxime Perrotin's avatar
Maxime Perrotin committed
210
211
212
213
214
215

    def redo(self):
        ''' Called when the redo button is pressed '''
        self.undo_stack.redo()
        self.check_state()
        self.current_hash = self.on_event()
216
        self.msc_redo.emit()
Maxime Perrotin's avatar
Maxime Perrotin committed
217

218
219
220
221
    def restore_global_state(self, statehash):
        ''' From a hashcode, restore a global state in the DLL (state +
            all internal variables '''
        target_state = self.set_of_states[statehash]
222
        for idx, (var, (sort, _)) in enumerate(self.proc.variables.viewitems()):
223
224
            # get internal variables, translate them to swig, and print them
            setter_ptr = getattr(self.dll, "_set_{}".format(var))
225
            value_asn1 = target_state[idx]
226
227
228
229
230
231
232
            try:
                value_swig_ptr = int(value_asn1._ptr)
            except TypeError:
                # for some types, swig uses a proxy class, in which case the
                # pointer is not in _ptr but in _ptr.this
                value_swig_ptr = int(value_asn1._ptr.this)
            value_ptr = ctypes.cast(value_swig_ptr,
233
234
235
                                    ctypes.POINTER(ctypes.c_uint32))
            setter_ptr(value_ptr)
        state_value = target_state[idx+1]
236
237
        set_state = getattr(self.dll, "_set_state")
        set_state(ctypes.c_char_p(state_value))
238
        #self.current_sdl_state = state_value
Maxime Perrotin's avatar
Maxime Perrotin committed
239
240
        #self.check_state()
        #self.on_event()
241

242
    def on_event(self, tc_name=None, param=None):
243
244
        ''' Update the list of global states and GUI after a TC has been sent
            This function does not trigger any undoable action '''
245
        complete_state = []
246
        # Read in the DLL the list of internal variables
Maxime Perrotin's avatar
Maxime Perrotin committed
247
        for var, (sort, _) in self.proc.variables.viewitems():
248
            typename = sort.ReferencedTypeName.replace('-', '_')
Maxime Perrotin's avatar
Maxime Perrotin committed
249
            get_size = getattr(self.dll, "{}_size".format(var))
250
            get_size.restype = ctypes.c_uint
Maxime Perrotin's avatar
Maxime Perrotin committed
251
            size = get_size()
252
253
254
            # ctypes hint: don't use c_char_p except for text strings
            get_value = getattr(self.dll, "{}_value".format(var))
            get_value.restype = ctypes.POINTER(ctypes.c_char)
255
256
257
258
259
            value = get_value()
            swig_ptr = ASN1.DV.new_byte_SWIG_PTR(size)
            for idx in xrange(size):
                ASN1.DV.byte_SWIG_PTR_setitem(swig_ptr,
                                              idx,
260
261
                                              ord(value[idx])
                                                if value[idx] else 0)
262
263
264
265
            asn1_instance = getattr(ASN1, typename)()
            setter = getattr(ASN1.DV, "SetDataFor_{}".format(typename))
            setter(asn1_instance._ptr, swig_ptr)
            gser = asn1_instance.GSER()
Maxime Perrotin's avatar
Maxime Perrotin committed
266
            as_pyside = vn.fromValueNotationToPySide(var, gser)
267
268
            self.asn1_editor.updateVariable(as_pyside,
                                            root=self.tree_items[var])
269
            complete_state.append(asn1_instance)   # not gser
270
        # Add the SDL state to the new global state
271
        complete_state.append(self.current_sdl_state)
272
        # And save this new state in the graph, if it was not there yet
273
274
        new_hash = hash(frozenset(complete_state))
        self.set_of_states[new_hash] = complete_state
275
276
277
278
279
280
281
282
283
284
        # Find the list of allowed TC based on the current state
        inputs = self.proc.mapping[self.current_sdl_state.lower()]
        allowed_tc = []
        for each in inputs:
            allowed_tc.extend(each.inputlist)
        # Remove timers from the list
        allowed_tc = set(allowed_tc) - set(self.proc.timers)
        # Enable/disable the parameterless TC buttons accordingly
        for tc, button in self.buttons.viewitems():
            if tc in self.proc.timers:
285
                # Ignore timers, they are handled differently, below
286
                continue
287
            button.setEnabled(tc in allowed_tc)
288
        if tc_name in self.timers:
Maxime Perrotin's avatar
Maxime Perrotin committed
289
            self.buttons[tc_name].setEnabled(False)
290
291
        # Emit the list of allowed TC for the GUI to update other buttons
        self.allowed_messages.emit(allowed_tc)
292
        if tc_name:
293
            # Log all TC sent
294
            self.log_area.addItem('Sent {}({})'.format(tc_name,
295
                                                       param or ''))
296
        return new_hash
297

298
299
300
301
302

    def start_simu(self):
        '''
           Set up the simulation bay, from simulation.ui file
           This panel handles parameterless signals and
303
304
           the simulation console
        '''
305
306
307
308
        ui = QFile(':/simulation.ui')
        loader = QUiLoader()
        widgets = loader.load(ui, parent=self.parent)
        dock = QDockWidget('Simulation bay', self.parent)
309
        dock.setFloating(False)
310
        #dock.resize(400, 400)
311
312
        dock.setObjectName('Simulation')
        self.parent.addDockWidget(Qt.RightDockWidgetArea, dock)
313
        #dock.setAllowedAreas(Qt.NoDockWidgetArea)
314
        dock.setWidget(widgets)
315
        dock.show()
316
317
        return dock

318
319
320
321
322
    @Slot()
    def add_to_msc(self, direction, msg):
        ''' Create an undo command and display message on the MSC diagram '''
        self.msc.emit(direction, msg, self.undo_stack)

323
324
325
326
    @Slot()
    def send_tc(self, name, tc_func_ptr, param=None):
        ''' Send a TC - Used either locally (parameterless TCs) or via
        a signal sent by the B-mapper-generated backends '''
327
        self.msc_macro_start.emit()
328
        with undo.UndoMacro(self.undo_stack, 'Send TC'):
Maxime Perrotin's avatar
Maxime Perrotin committed
329
330
331
332
333
334
335
            if name in self.timers:
                self.add_to_msc('timeout', name)
            else:
                msg = '{tc}{arg}'.format(tc=name,
                                         arg='({})'.format(param.GSER())
                                             if param else '')
                self.add_to_msc('out', msg)
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
            old_state = self.current_hash

            # Send the TC
            if param:
                # Cast the SWIG type (ASN.1 Native format) to a ctypes pointer
                try:
                    swig_ptr = int(param._ptr)
                except TypeError:
                    # when swig uses a proxy class, pointer is in _ptr.this
                    swig_ptr = int(param._ptr.this)
                param_ptr = ctypes.cast(swig_ptr,
                                        ctypes.POINTER(ctypes.c_uint32))
                tc_func_ptr(param_ptr)
            else:
                tc_func_ptr()

352
            self.check_state()
353
354
355
356
            # Update windows, highlight state, enable/disable buttons, etc.
            self.current_hash = self.on_event(tc_name=name,
                                              param=param.GSER() if param
                                                                 else None)
357

358
359
360
            # Create the Undo command to restore the previous state
            undo_cmd = SendTC(self, old_state)
            self.undo_stack.push(undo_cmd)
361
        self.msc_macro_stop.emit()
Maxime Perrotin's avatar
Maxime Perrotin committed
362

Maxime Perrotin's avatar
Maxime Perrotin committed
363
    def receive_tm(self, tm_name):
364
        ''' Callback function when a paramless TM is received '''
365
        self.add_to_msc('in', tm_name)
Maxime Perrotin's avatar
Maxime Perrotin committed
366
        self.log_area.addItem('Received event "{}"'.format(tm_name))
Maxime Perrotin's avatar
Maxime Perrotin committed
367

Maxime Perrotin's avatar
Maxime Perrotin committed
368
369
    def set_timer(self, name, duration):
        ''' Callback function when the SDL model sets a timer '''
370
        self.add_to_msc('set', 'SET_{}_{}'.format(name, duration))
Maxime Perrotin's avatar
Maxime Perrotin committed
371
372
        self.log_area.addItem('Received event "SET_{}({})"'
                              .format(name, duration))
373
        self.buttons[name].setEnabled(True)
Maxime Perrotin's avatar
Maxime Perrotin committed
374
375
376

    def reset_timer(self, name):
        ''' Callback function when the SDL model resets a timer '''
377
        self.add_to_msc('reset', 'RESET_{}'.format(name))
Maxime Perrotin's avatar
Maxime Perrotin committed
378
379
        self.log_area.addItem('Received event "RESET_{}"'
                              .format(name))
380
        self.buttons[name].setEnabled(False)
Maxime Perrotin's avatar
Maxime Perrotin committed
381

382
383
    def set_paramless_tc(self):
        ''' Once the DLL is loaded set the buttons to send paramless TC '''
Maxime Perrotin's avatar
Maxime Perrotin committed
384
        # tc_area and buttons have to be in self because of known pyside bug
385
        widget = self.dock_simu.widget()
Maxime Perrotin's avatar
Maxime Perrotin committed
386
        self.tc_area = widget.findChild(QGridLayout, 'tc_grid')
387
        # Find parameterless input signals and create buttons
Maxime Perrotin's avatar
Maxime Perrotin committed
388
        self.buttons = {}
389
390
        for each in self.proc.input_signals:
            if 'type' not in each:
Maxime Perrotin's avatar
Maxime Perrotin committed
391
                self.buttons[each['name']] = QPushButton(each['name'])
392
        for each in self.proc.timers:
Maxime Perrotin's avatar
Maxime Perrotin committed
393
394
395
            self.buttons[each] = QPushButton(each+' timeout')
        for name, button in self.buttons.viewitems():
            self.tc_area.addWidget(button)
396
397
            tc = getattr(self.dll, '{}_{}'.format(self.proc.processName,
                                                  name))
398
            button.pressed.connect(partial(self.send_tc, name, tc))
399

Maxime Perrotin's avatar
Maxime Perrotin committed
400
401
402
403
404
405
406
407
408
409
410
411
412
    def set_paramless_tm(self):
        ''' Once the DLL is loaded register the paramless TM to log them '''
        widget = self.dock_simu.widget()
        self.log_area = widget.findChild(QListWidget, 'log_tm')
        # Define a single function to handle all parameterless TM
        func = ctypes.CFUNCTYPE(None, ctypes.c_char_p)
        self.tm_func = func(self.receive_tm)
        for each in self.proc.output_signals:
            if 'type' not in each:
                register_func = getattr(self.dll, 'register_{}'
                                                   .format(each['name']))
                register_func(self.tm_func)

413
414
    def init_timers(self):
        ''' When loading the DLL, initialize timers/set callbacks, etc '''
Maxime Perrotin's avatar
Maxime Perrotin committed
415
416
417
418
419
        # Define a single function to handle all timer SET functions
        set_func = ctypes.CFUNCTYPE(None, ctypes.c_char_p, ctypes.c_int)
        reset_func = ctypes.CFUNCTYPE(None, ctypes.c_char_p)
        self.set_timer_wrap = set_func(self.set_timer)
        self.reset_timer_wrap = reset_func(self.reset_timer)
420
421
422
        for each in self.proc.timers:
            self.timers[each] = 'unset'
            self.buttons[each].setEnabled(False)
Maxime Perrotin's avatar
Maxime Perrotin committed
423
424
425
426
427
428
            register_set_func = getattr(self.dll, 'register_SET_{}'
                                                  .format(each))
            register_set_func(self.set_timer_wrap)
            register_reset_func = getattr(self.dll, 'register_RESET_{}'
                                                    .format(each))
            register_reset_func(self.reset_timer_wrap)