standalone_editor.py 12.2 KB
Newer Older
1
2
3
4
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
    TASTE ASN.1 Value Editor
Maxime Perrotin's avatar
Maxime Perrotin committed
5
6
7
8

    PySide widget that opens an ASN.1 variable editor
    and returns a GSER string when done

9
    Example of use from the command line:
Maxime Perrotin's avatar
Maxime Perrotin committed
10
11
12
13
14
    ./standalone_editor.py  -a test/TPos.asn -t T-POS \
          -d 'myIntSet: { data4 -1310720, data1 0, data3 -1024, data2 -100.0 }'

    The SingleValueEditor widget can also be embedded in another Qt application

15
16
17
18
19
20
21
22
23
24
    This version needs ASN.1 version 3.x.x

    Copyright (c) 2012-2015 European Space Agency

    Designed and implemented by Maxime Perrotin

    Contact: maxime.perrotin@esa.int

    License is LGPLv3 - Check the LICENSE file
"""
Maxime Perrotin's avatar
Maxime Perrotin committed
25
26
27
28
29
30
31
32

import subprocess
import signal
import sys
import optparse
import os
import logging
import tempfile
Maxime Perrotin's avatar
Maxime Perrotin committed
33
import traceback
Maxime Perrotin's avatar
Maxime Perrotin committed
34
35
36
37
from shutil import rmtree

import resources

38
__author__  = 'Maxime Perrotin'
39
__licence__ = 'LGPL v3'
40
__url__     = 'http://taste.tuxfamily.org'
Maxime Perrotin's avatar
Maxime Perrotin committed
41

Maxime Perrotin's avatar
Maxime Perrotin committed
42
43
44
from ColorFormatter import ColorFormatter

# Set up the logging facilities
45
logger  = logging.getLogger(__name__)
Maxime Perrotin's avatar
Maxime Perrotin committed
46
47
48
49
console = logging.StreamHandler(sys.__stdout__)
console.setFormatter(ColorFormatter())
logger.addHandler(console)

Maxime Perrotin's avatar
Maxime Perrotin committed
50
51
52
53
54
try:
    from PySide.QtCore import QFile
    from PySide.QtCore import QObject
    from PySide.QtGui import QApplication, QToolButton
    from PySide.QtUiTools import QUiLoader
Maxime Perrotin's avatar
Maxime Perrotin committed
55
56
    import opengeode.Asn1scc as asn1scc
    asn1scc.LOG = logger
Maxime Perrotin's avatar
Maxime Perrotin committed
57
except ImportError:
58
59
    logger.error('PySide or OpenGEODE import error')
    sys.exit(1)
Maxime Perrotin's avatar
Maxime Perrotin committed
60
61
62
63

try:
    UI_FILE = ':/singleEditor.ui'
    from asn1_value_editor import asn1Editor
Maxime Perrotin's avatar
Maxime Perrotin committed
64
    from vn import valueNotationToCTypes
65
66
67
except ImportError:
    logger.error('ASN1 Value Editor import error')
    sys.exit(1)
Maxime Perrotin's avatar
Maxime Perrotin committed
68

69
70
71
72
73
74
75
# DV.py is generated from asn2dataModel (DMT tools)
# It containts constants related to the enumerated and choice types
# If not present it will be generated on the fly using opengeode's API
try:
    import DV
except ImportError:
    DV = None
Maxime Perrotin's avatar
Maxime Perrotin committed
76

77
78
79
80

def import_DV(asnFile=''):  # type: str -> Module
    ''' Generate DV.py from the user-provided asn1 file '''
    global DV
81
    if not DV or asnFile:
82
83
84
        try:
            module = asn1scc.asn2dataModel(asnFile)
            DV = module.DV
85
            return module
86
87
88
89
        except TypeError as err:
            logger.error('Could not execute asn2dataModel: ' + str(err))
            sys.exit(1)

Maxime Perrotin's avatar
Maxime Perrotin committed
90
91
92
def asn1sccToasn1ValueEditorTypes(dataview, name, asntype):
    ''' Convert an ASN.1 type from the Python AST of asn1scc v3
        to the AST of the value editor '''
93
    assert DV is not None
Maxime Perrotin's avatar
Maxime Perrotin committed
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
    result = {'nodeTypename': name}
    t = asntype
    # find basic type
    while t.kind == 'ReferenceType':
        t = dataview[t.ReferencedTypeName].type
    if t.kind == 'IntegerType':
        result['type'] = 'INTEGER'
        result['id'] = name
        result['minR'] = int(t.Min)
        result['maxR'] = int(t.Max)
    elif t.kind == 'RealType':
        result['type'] = 'REAL'
        result['id'] = name
        result['minR'] = float(t.Min)
        result['maxR'] = float(t.Max)
    elif t.kind == 'BooleanType':
        result['type'] = 'BOOLEAN'
        result['id'] = name
        result['default'] = 'False'
    elif t.kind == 'SequenceType':
        result['type'] = 'SEQUENCE'
        result['id'] = name
        result['children'] = []
        for child, childtype in t.Children.viewitems():
            result['children'].append(
                asn1sccToasn1ValueEditorTypes(dataview, child, childtype.type))
    elif t.kind == 'SequenceOfType':
        result['type'] = 'SEQOF'
        result['id'] = name
        result['minSize'] = int(t.Min)
        result['maxSize'] = int(t.Max)
        result['seqoftype'] = \
                asn1sccToasn1ValueEditorTypes(dataview, '', t.type)
    elif t.kind == 'EnumeratedType':
        result['type'] = 'ENUMERATED'
        result['id'] = name
        result['values'] = t.EnumValues.keys()
Maxime Perrotin's avatar
Maxime Perrotin committed
131
132
        result['valuesInt'] = {val: num.IntValue
                               for val, num in t.EnumValues.viewitems()}
Maxime Perrotin's avatar
Maxime Perrotin committed
133
134
135
136
137
138
139
    elif t.kind == 'ChoiceType':
        result['type'] = 'CHOICE'
        result['id'] = name
        result['choices'] = []
        for child, childtype in t.Children.viewitems():
            result['choices'].append(
                asn1sccToasn1ValueEditorTypes(dataview, child, childtype.type))
140
        result['choiceIdx'] = {enumerant: getattr(DV, chty.EnumID)
Maxime Perrotin's avatar
Maxime Perrotin committed
141
                               for enumerant, chty in t.Children.viewitems()}
Maxime Perrotin's avatar
Maxime Perrotin committed
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
    elif t.kind.endswith('StringType'):
        result['type'] = 'STRING'
        result['id'] = name
        result['minSize'] = int(t.Min)
        result['maxSize'] = int(t.Max)
    else:
        print('[ERROR] Unsupported type:', t.kind)
        sys.exit(-1)
    return result


class SingleValueEditor(QObject):
    ''' Main class '''
    def __init__(self, asnFile, paramsAndTypes=None, parent=None):
        '''
        Load the ASN.1 file and create the widgets for all parameters passed
        as arguments (once for all).
        When editParam value is called with parameter not defined in the
        constructor, then a new widget is created por this parameter.
        '''
        super(SingleValueEditor, self).__init__(parent)
        self.widgets = {}
        self.types = None
        self.initializedWidgets = False
        self.parent = parent
        self.tc = {}

169
170
        # Make sure DV.py is present and imported
        self.asn1ctypes = import_DV(asnFile)
171

Maxime Perrotin's avatar
Maxime Perrotin committed
172
        try:
Maxime Perrotin's avatar
Maxime Perrotin committed
173
174
175
176
            logger.info(asnFile)
            dataView = asn1scc.parse_asn1([asnFile],
                               ast_version=asn1scc.ASN1.UniqueEnumeratedNames,
                               flags=[asn1scc.ASN1.AstOnly])
Maxime Perrotin's avatar
Maxime Perrotin committed
177
            self.ASN1_AST = dataView.types
Maxime Perrotin's avatar
Maxime Perrotin committed
178
        except (ImportError, NameError, TypeError) as err:
179
180
181
            logger.error('Error loading ASN.1 model')
            logger.debug(str(err))
            logger.debug(traceback.format_exc())
Maxime Perrotin's avatar
Maxime Perrotin committed
182
            raise
183

Maxime Perrotin's avatar
Maxime Perrotin committed
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
        # convert dataView types to a list of tc as needed by asn1-value-editor
        for t, v in dataView.types.items():
            self.tc[t] = asn1sccToasn1ValueEditorTypes(dataView.types, t, v.type)

        logger.debug("Types found in '%s' asn file: \n\t%s",
                 asnFile, self.tc.keys())

    def initWidgets(self):
        uiFile = QFile(UI_FILE)
        loader = QUiLoader()
        loader.registerCustomWidget(asn1Editor)
        for asnType in self.tc.keys():
            uiFile.open(QFile.ReadOnly)
            self.widgets[asnType] = {'widget': loader.load(uiFile, parent=self)}
            uiFile.close()

            widget = self.widgets[asnType]['widget']
            try:
202
203
                # If the parent is a widget, this will place the window
                # in the center of the parent
Maxime Perrotin's avatar
Maxime Perrotin committed
204
205
206
207
208
209
                widget.setParent(self.parent)
            except:
                logger.info('No parent widget - window will be placed randomly')

            pyType = self.tc[asnType]

210
211
            self.widgets[asnType]['editor'] = widget.findChild(asn1Editor,
                                                               'ASN1EDITOR')
Maxime Perrotin's avatar
Maxime Perrotin committed
212
213
214
215
216
217
218
219
220
            self.widgets[asnType]['editor'].log = logger
            okButton = widget.findChild(QToolButton, 'okButton')
            cancelButton = widget.findChild(QToolButton, 'cancelButton')
            okButton.clicked.connect(widget.accept)
            cancelButton.clicked.connect(widget.reject)
            # Hide columns showing the ASN.1 type and constraint
            self.widgets[asnType]['editor'].hideExtraColumns()
            self.widgets[asnType]['editor'].setAsn1Model(pyType)
        self.initializedWidgets = True
Maxime Perrotin's avatar
Maxime Perrotin committed
221
        logger.info("Succesfully created new Single Value Editor")
Maxime Perrotin's avatar
Maxime Perrotin committed
222
223
224


    def _asnTypeWidget(self, name, asnType):
225
        logger.debug("Creating new asnType widget. Name: %(pname)s, asnType: %(asnType)s.",
Maxime Perrotin's avatar
Maxime Perrotin committed
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
                     {"pname": name, "asnType": asnType})

        if name in self.widgets:
            logger.warning("""Try to create widget "%(pname)s" that exist.""",
                           {"pname": name})
            return

        # Create widget
        uiFile = QFile(UI_FILE)
        loader = QUiLoader()
        loader.registerCustomWidget(asn1Editor)
        uiFile.open(QFile.ReadOnly)
        self.widgets[name] = {'widget': loader.load(uiFile)}
        uiFile.close()
        widget = self.widgets[name]['widget']
        pyType = self.tc[name.replace('_', '-')]
        self.widgets[name]['editor'] = widget.findChild(asn1Editor, 'ASN1EDITOR')
        self.widgets[name]['editor'].log = logger
        okButton = widget.findChild(QToolButton, 'okButton')
        cancelButton = widget.findChild(QToolButton, 'cancelButton')
        okButton.clicked.connect(widget.accept)
        cancelButton.clicked.connect(widget.reject)
        # Hide columns showing the ASN.1 type and constraint
        self.widgets[name]['editor'].hideExtraColumns()
        self.widgets[name]['editor'].setAsn1Model(pyType)

252
253
    def setupEdit(self, param, defValue=None, asnType=None):
        ''' Initialize the parameter edition '''
Maxime Perrotin's avatar
Maxime Perrotin committed
254
255
256
257
258
259
260
261
262
263
        if not self.initializedWidgets:
            self.initWidgets()

        if not asnType:
            asnType = param

        if param not in self.widgets:
            self._asnTypeWidget(param, asnType)

        widget = self.widgets[param]
264
        # Create a ctypes instance of the variable and set it to the editor
Maxime Perrotin's avatar
Maxime Perrotin committed
265
266
        instance = getattr(self.asn1ctypes, asnType.replace('-', '_'))()
        widget['editor'].asn1Instance = instance
Maxime Perrotin's avatar
Maxime Perrotin committed
267
        if defValue:
Maxime Perrotin's avatar
Maxime Perrotin committed
268
269
270
271
272
273
            valueNotationToCTypes(gser=defValue,
                                  dest=instance,
                                  sort=self.ASN1_AST[asnType].type,
                                  ASN1Mod=self.asn1ctypes,
                                  ASN1_AST=self.ASN1_AST)
            widget['editor'].updateVariable()
274
275
276
277
278
279
280
281
        return widget

    def editValue(self, param, defValue=None):
        return self.editParam(param, defValue)

    def editParam(self, param, defValue=None, asnType=None):
        ''' Edit parameter (open widget,  wait for OK/Cancel and read value) '''
        widget = setupEdit(param, defValue, asnType)
Maxime Perrotin's avatar
Maxime Perrotin committed
282
283
        response = widget['widget'].exec_()
        if response:
284
            data = widget['editor'].getVariable(dest=widget['editor'].asn1Instance)
Maxime Perrotin's avatar
Maxime Perrotin committed
285
            return widget['editor'].asn1Instance.GSER()
Maxime Perrotin's avatar
Maxime Perrotin committed
286
287
288
289
290
291
292
        else:
            return None

    def asn1Types(self):
        u""" Return the list of types declared within asn file """
        return self.tc.keys()

293
294

def main():
Maxime Perrotin's avatar
Maxime Perrotin committed
295
296
297
    # Exit app on Ctrl-C
    signal.signal(signal.SIGINT, signal.SIG_DFL)

298
299
    usage = 'usage: asn1_value_editor -a file.asn -t type [-d default value]'
    version = 'ASN.1 Value Editor'
Maxime Perrotin's avatar
Maxime Perrotin committed
300

Maxime Perrotin's avatar
Maxime Perrotin committed
301
    logger.info('Starting ASN.1 Value Editor')
Maxime Perrotin's avatar
Maxime Perrotin committed
302
303
304
305
306
307
308
309
310

    # Parse the command line
    parser = optparse.OptionParser(usage=usage, version=version)
    parser.add_option('-v', '--verbose', action='store_true', default=False, help='Display debug information')
    parser.add_option('-a', '--asn', dest='asn', metavar='ASN.1 File', help='ASN.1 module')
    parser.add_option('-t', '--type', dest='asnType', help='Type of the variable you want to edit')
    parser.add_option('-d', '--value', dest='asnValue', help='Value of the variable you want to edit')
    options, args = parser.parse_args()
    if options.verbose:
Maxime Perrotin's avatar
Maxime Perrotin committed
311
        logger.setLevel(logging.DEBUG)
Maxime Perrotin's avatar
Maxime Perrotin committed
312
    else:
Maxime Perrotin's avatar
Maxime Perrotin committed
313
        logger.setLevel(logging.INFO)
Maxime Perrotin's avatar
Maxime Perrotin committed
314
315

    if options.asn is None or options.asnType is None:
Maxime Perrotin's avatar
Maxime Perrotin committed
316
        logger.error('You must specify an ASN.1 file and specify a type. Check --help for more info')
Maxime Perrotin's avatar
Maxime Perrotin committed
317
318
        sys.exit(-1)

Maxime Perrotin's avatar
Maxime Perrotin committed
319
320
    logger.debug('ASN.1 File  = ' + options.asn)
    logger.debug('ASN.1 Type  = ' + options.asnType)
Maxime Perrotin's avatar
Maxime Perrotin committed
321
    if options.asnValue is not None:
Maxime Perrotin's avatar
Maxime Perrotin committed
322
        logger.debug('ASN.1 Value = ' + options.asnValue)
Maxime Perrotin's avatar
Maxime Perrotin committed
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340

    # Define a Qt application (mandatory)
    app = QApplication(sys.argv)

    # Select a nice style for uniform rendering among all platforms
    app.setStyle("cleanlooks")

    # Initialize the editor with a dictionary of pairs ParamName/ParamType:
    s = SingleValueEditor(options.asn, paramsAndTypes={'param': options.asnType})

    # Open the value editor and retrieve the value, if Cancel has not been pressed
    result = s.editParam(options.asnType, options.asnValue)
    if result is not None:
        print result
        sys.exit(0)
    else:
        sys.exit(1)
    app.exec_()
341
342
343
344


if __name__ == '__main__':
    main()