source: modrana.py @ 7f6b0e4

Last change on this file since 7f6b0e4 was 7f6b0e4, checked in by Martin Kolman <martin.kolman@…>, 2 years ago

export GUI toolkit string before the device module is loaded

  • Property mode set to 100755
File size: 25.2 KB
Line 
1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3#----------------------------------------------------------------------------
4# Rana main GUI.  Displays maps, for use on a mobile device
5#
6# Controls:
7#   * click on the overlay text to change fields displayed
8#----------------------------------------------------------------------------
9# Copyright 2007-2008, Oliver White
10#
11# This program is free software: you can redistribute it and/or modify
12# it under the terms of the GNU General Public License as published by
13# the Free Software Foundation, either version 3 of the License, or
14# (at your option) any later version.
15#
16# This program is distributed in the hope that it will be useful,
17# but WITHOUT ANY WARRANTY; without even the implied warranty of
18# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19# GNU General Public License for more details.
20#
21# You should have received a copy of the GNU General Public License
22# along with this program.  If not, see <http://www.gnu.org/licenses/>.
23#----------------------------------------------------------------------------
24
25# module folder name
26modulesFolder = 'modules'
27import sys
28# add module folder to path
29sys.path.append(modulesFolder)
30import time
31startTimestamp = time.time()
32import math
33# set current directory to the directory
34# of this file
35# like this, modRana can be run from an absolute path
36# eq.: ./opt/modrana/modrana.py -u QML -d n9
37import os
38os.chdir(os.path.dirname(os.path.abspath(__file__)))
39
40import marshal
41import traceback
42from math import radians
43# import core modules/classes
44from core import startup
45from core import paths
46from core import configs
47from core import gs
48
49# record that imports-done timestamp
50importsDoneTimestamp = time.time()
51
52def createFolderPath(newPath):
53  """
54  Creat a path for a directory and all needed parent forlders
55  -> parent directoryies will be created
56  -> if directory already exists, then do nothing
57  -> if there is another filsystem object (like a file) with the same name, raise an exception
58  """
59  if not newPath:
60    print("cannot create folder, wrong path: ", newPath)
61    return False
62  if os.path.isdir(newPath):
63    return True
64  elif os.path.isfile(newPath):
65    print("cannot create directory, file already exists: '%s'" % newPath)
66    return False
67  else:
68    print("creating path: %s" % newPath)
69    head, tail = os.path.split(newPath)
70    if head and not os.path.isdir(head):
71        mkdirs(head)
72    if tail:
73        os.mkdir(newPath)
74    return True
75
76def simplePythagoreanDistance(x1, y1, x2, y2):
77  """convenience PyThagorean distance :)"""
78  dx = x2 - x1
79  dy = y2 - y1
80  return math.sqrt(dx**2 + dy**2)
81
82class ModRana:
83  """
84  This is THE main modRana class.
85  """
86  def __init__(self):
87    self.timing = []
88    self.addCustomTime("modRana start",startTimestamp)
89    self.addCustomTime("imports done", importsDoneTimestamp)
90
91    # constants & variable initilization
92    self.dmod = None # device specific module
93    self.gui = None
94
95    self.d = {} # persistant dictionary of data
96    self.m = {} # dictionary of loaded modules
97    self.watches = {} # List of data change watches
98    self.maxWatchId = 0
99
100    self.mapRotationAngle = 0 # in radians
101    self.notMovingSpeed = 1 # in m/s
102   
103    # map center shifting variables
104    self.centerShift = (0,0)
105
106    # map layers
107    self.mapLayers = {}
108
109    # per mode options
110    # NOTE: this variable is automatically saved by the
111    # options module
112    self.keyModifiers = {}
113
114    # start timing modRana launch
115    self.addTime("GUI creation")
116
117    # add the startup handling core module
118    start = startup.Startup(self)
119    self.args = start.getArgs()
120
121    # add the paths handling core module
122    self.paths = paths.Paths(self)
123    # add the configs handling core module
124    self.configs = configs.Configs(self)
125
126    # load persistant options
127    self.optLoadingOK= self._loadOptions()
128
129    # check if upgrade took place
130
131    if self.optLoadingOK:
132      savedVersionString = self.get('modRanaVersionString', "")
133      versionStringFromFile = self.paths.getVersionString()
134      if savedVersionString != versionStringFromFile:
135        print "modRana: possible upgrade detected"
136        self._postUpgradeCheck()
137
138    # save current version string
139    self.set('modRanaVersionString', self.paths.getVersionString())
140
141    # load all configuration files
142    self.configs.loadAll()
143
144    # start loading modules
145    self.loadModules()
146    # startup done, print some statistics
147    self._startupDone()
148
149  def _postUpgradeCheck(self):
150    """
151    perform post upgrade checks
152    """
153    self.configs.upgradeConfigFiles()
154
155  ##  MODULE HANDLING ##
156
157  def loadModules(self):
158    """Load all modules from the specified directory"""
159
160    # get the devicme module string
161    # (a unique device module string identificator)
162    # make sure there is some argument provided
163    if self.args:
164      device = self.args.d[0]
165    else:
166      # use the Neo device module as fallback
167      device = "neo"
168
169    device = device.lower() # convert to lowercase
170    print "importing modules:"
171    start = time.clock()
172    self.initInfo={
173              'modrana': self,
174              'device': device, # TODO: do this directly
175              'name': ""
176             }
177
178    # make shortcut for the loadModule function
179    loadModule = self._loadModule
180
181    # make sure there are some arguments
182    if self.args.u:
183      GUIString = self.args.u[0]
184    else:
185      # GTK GUI fallback
186      GUIString = "GTK"
187    gs.GUIString = GUIString
188
189    ## load the device specific module
190
191    # NOTE: other modules need the device and GUI modules
192    # during init
193    deviceModulesPath = os.path.join(modulesFolder, "device_modules")
194    sys.path.append(deviceModulesPath)
195    dmod = loadModule("device_%s" % device, "device")
196    if dmod == None:
197      print("modRana: no device module name provided"
198            "loading the Neo device module as failsafe")
199      device = "neo"
200      dmod = loadModule("device_%s" % device, "device")
201    self.dmod = dmod
202
203    ## load the GUI module
204
205    # add the GUI module folder to path
206    GUIModulesPath = os.path.join(modulesFolder, "gui_modules")
207    sys.path.append(GUIModulesPath)
208    if GUIString == "GTK":
209      gui = loadModule("gui_gtk", "gui")
210    elif GUIString == "QML":
211      gui = loadModule("gui_qml", "gui")
212      # make device module available to the GUI module
213    if gui:
214      gui.dmod = dmod
215    self.gui = gui
216
217
218    # get possible module names
219    moduleNames = self._getModuleNamesFromFolder(modulesFolder)
220    # load if possible
221    for moduleName in moduleNames:
222        # filter out .py
223        moduleName = moduleName.split('.')[0]
224        loadModule(moduleName, moduleName[4:])
225
226    print "Loaded all modules in %1.2f ms, initialising" % (1000 * (time.clock() - start))
227    self.addTime("all modules loaded")
228
229    # make sure all modules have the device module and other variables before first time
230    for m in self.m.values():
231      m.modrana = self # make this class accessible from modules
232      m.dmod = self.dmod
233
234      # run what needs to be done before firstTime is called
235#    self._modulesLoadedPreFirstTime()
236
237    start = time.clock()
238    for m in self.m.values():
239      m.firstTime()
240
241      # run what needs to be done after firstTime is called
242    self._modulesLoadedPostFirstTime()
243
244    print( "Initialization complete in %1.2f ms" % (1000 * (time.clock() - start)) )
245
246    # add last timing checkpoint
247    self.addTime("all modules initilaized")
248
249  def _getModuleNamesFromFolder(self,folder):
250    """list a given folder and find all possible module names"""
251    return filter(lambda x: x[0:4]=="mod_" and x[-4:]!=".pyc",os.listdir(folder))
252
253  def _loadModule(self, importName, modRanaName):
254    """load a single module by name from path"""
255    startM = time.clock()
256    try:
257      a = __import__(importName)
258      initInfo = self.initInfo
259      name = modRanaName
260      initInfo['name'] = name
261      module = a.getModule(self.m, self.d, initInfo)
262      self.m[name] = module
263      print( " * %s: %s (%1.2f ms)" % (name, self.m[name].__doc__, (1000 * (time.clock() - startM))) )
264      return module
265    except Exception, e:
266      print( "modRana: module: %s/%s failed to load" % (importName, modRanaName) )
267      traceback.print_exc(file=sys.stdout) # find what went wrong
268      return None
269
270  def _modulesLoadedPreFirstTime(self):
271    """this is run after all the modules have been loaded,
272    but before their first time is called"""
273    self._updateCenteringShiftCB()
274
275    """to only update values needed for map drawing when something changes
276       * window is resized
277       * user switches something releated in options
278       * etc.
279       we use the key watching mechanism
280       once a related key is changed, we update all the values
281       """
282    # watch both centering shift related variables
283    self.watch('posShiftAmount', self._updateCenteringShiftCB)
284    self.watch('posShiftDirection', self._updateCenteringShiftCB)
285    # also watch the viewport
286    self.watch('viewport', self._updateCenteringShiftCB)
287    # and map scaling
288    self.watch('mapScale', self._updateCenteringShiftCB)
289    # and mode change
290    self.watch('mode', self._modeChangedCB)
291    # cache key modifiers
292    self.keyModifiers = self.d.get('keyModifiers', {})
293    # check if own Quit button is needed
294    if self.dmod.needsQuitButton():
295      menus = self.m.get('menu', None)
296      if menus:
297        menus.addItem('main', 'Quit', 'quit', 'menu:askQuit')
298
299  def _modulesLoadedPostFirstTime(self):
300    """this is run after all the modules have been loaded,
301    after before their first time is called"""
302   
303    # check if redrawing time should be printed to terminal
304    if 'showRedrawTime' in self.d and self.d['showRedrawTime'] == True:
305      self.showRedrawTime = True
306
307  def getModule(self, name, default=None):
308    """
309    return a given module instance, return default if no instance
310    with given name is found
311    """
312    return self.m.get(name, default)
313
314  def getModules(self):
315    """
316    return the dictionary of all loaded modules
317    """
318    return self.m
319
320  def update(self):
321    """perform module state update"""
322    # TODO: depreciate this
323    # in favor of event based and explicit update timers
324    for m in self.m.values():
325      m.update()
326
327  ## STARTUP AND SHUTDOWN ##
328
329  def _startupDone(self):
330    """called when startup has been finished"""
331
332    # report startup time
333    self.reportStartupTime()
334
335    # check if loading options failed
336    if self.optLoadingOK:
337      self.gui.notify("Loading saved options failed", 7000)
338
339    # start the mainloop or equivalent
340    self.gui.startMainLoop()
341
342  def shutdown(self):
343    """
344    start shutdown cleanup and stop GUI main loop
345    when finished
346    """
347    print("Shutting-down modules")
348    for m in self.m.values():
349      m.shutdown()
350    self._saveOptions()
351    time.sleep(2) # leave some times for threads to shut down
352    print("Shuttdown complete")
353   
354  ## OPTIONS SETTING AND WATCHING ##
355
356  def get(self, name, default=None, mode=None):
357    """Get an item of data"""
358
359    # check if the value depends on current mode
360    if name in self.keyModifiers.keys():
361      # get the current mode
362      if mode == None:
363        mode = self.d.get('mode', 'car')
364      if mode in self.keyModifiers[name]['modes'].keys():
365        # get the dictionary with per mode values
366        multiDict = self.d.get('%s#multi' % name , {})
367        # retrun the value for current mode
368        return multiDict.get(mode,default)
369      else:
370        return(self.d.get(name, default))
371
372    else: # just return the normal value
373      return(self.d.get(name, default))
374
375  def set(self, name, value, save=False, mode=None):
376    """Set an item of data,
377    if there is a watch set for this key,
378    notify the watcher that its value has changed"""
379
380    oldValue = self.get(name, value)
381
382    if name in self.keyModifiers.keys():
383      # get the current mode
384      if mode == None:
385        mode = self.d.get('mode', 'car')
386      # check if there is a modifier for the current mode
387      if mode in self.keyModifiers[name]['modes'].keys():
388        # save it to the name + #multi key under the mode key
389        try:
390          self.d['%s#multi' % name][mode] = value
391        except KeyError: # key not yet created
392          self.d['%s#multi' % name] = {mode : value}
393      else: # just save to the key as usuall
394        self.d[name] = value
395    else: # just save to the key as usuall
396      self.d[name] = value
397
398    self._notifyWatcher(name, oldValue)
399    """options are normally saved on shutdown,
400    but for some data we want to make sure they are stored and not
401    los for example becuase of power outage/empty battery, etc."""
402    if save:
403      options = self.m.get('options')
404      if options:
405        options.save()
406
407  def optionsKeyExists(self, key):
408    """Report if a given key exists"""
409    return key in self.d.keys()
410
411  def purgeKey(self, key):
412    """remove a key from the presistant dictionary,
413    including possible key modifiers and alternate values"""
414    if key in self.d:
415      oldValue = self.get(key, None)
416      del self.d[key]
417      # purge any key modifiers
418      if key in self.keyModifiers.keys():
419        del self.keyModifiers[key]
420        """also remove the possibly present
421        alternative states for different modes"""
422        multiKey = "%s#multi" % key
423        if multiKey in self.d:
424          del self.d[multiKey]
425      self._notifyWatcher(key, oldValue)
426      return True
427    else:
428      print("modrana: can't purge a not present key: %s" % key)
429
430  def watch(self, key, callback, *args):
431    """add a callback on an options key
432    callback will get:
433    key, newValue, oldValue, *args
434
435    NOTE: watch ids should be >0, so that they evaluate as True
436    """
437    nrId = self.maxWatchId + 1
438    id = "%d_%s" % (nrId,key)
439    self.maxWatchId = nrId # TODO: recycle ids ? (alla PID)
440    if key not in self.watches:
441      self.watches[key] = [] # create the initial list
442    self.watches[key].append((id,callback,args))
443    return id
444
445  def removeWatch(self, id):
446    """remove watch specified by the given watch id"""
447    (nrId, key) = id.split('_')
448
449    if key in self.watches:
450      remove = lambda x:x[0]==id
451      self.watches[key][:] = [x for x in self.watches[key] if not remove(x)]
452    else:
453      print("modRana: can't remove watch - key does not exist, watchId:", id)
454
455  def _notifyWatcher(self, key, oldValue):
456    """run callbacks registered on an options key
457    HOW IT WORKS
458    * the watcher is notified before the key is written to the persistant
459    dictionary, so that it can react before the change is visible
460    * the watcher gets the key and both the new and old values
461    """
462    callbacks = self.watches.get(key, None)
463    if callbacks:
464      for item in callbacks:
465        (id,callback,args) = item
466        # rather supply the old value than None
467        newValue = self.get(key, oldValue)
468        if callback:
469          if callback(key,oldValue, newValue, *args) == False:
470            # remove watches that return False
471            self.removeWatch(id)
472        else:
473          print("invalid watcher callback :", callback)
474
475  def addKeyModifier(self, key, modifier=None, mode=None, copyInitialValue=True):
476    """add a key modifier
477    NOTE: currently only used to make value of some keys
478    dependent on the current mode"""
479    options = self.m.get('options', None)
480    # remeber the old value, if not se use default from options
481    # if available
482    if options:
483      defaultValue = options.getKeyDefault(key, None)
484    else:
485      defaultValue = None
486    oldValue = self.get(key,defaultValue)
487    if mode == None:
488      mode = self.d.get('mode', 'car')
489    if key not in self.keyModifiers.keys(): # initialize
490      self.keyModifiers[key] = {'modes':{mode:modifier}}
491    else:
492      self.keyModifiers[key]['modes'][mode] = modifier
493
494    # make sure the multi mode dictionary exists
495    multiKey = '%s#multi' % key
496    multiDict = self.d.get(multiKey , {})
497    self.d[multiKey] = multiDict
498
499    """if the modifier is set for the first time,
500    do we copy the value from the normal key or not ?"""
501    if copyInitialValue:
502      # check if the key is unset for this mode
503      if mode not in multiDict:
504        # set for first time, copy value       
505        self.set(key, self.d.get(key, defaultValue), mode=mode)
506
507    # notify watchers
508    self._notifyWatcher(key, oldValue)
509
510  def removeKeyModifier(self, key, mode=None):
511    """remove key modifier
512    NOTE: currently this just makes the key independent
513    on the current mode"""
514
515
516    # if no mode is provided, use the current one
517    if mode == None:
518        mode = self.d.get('mode', 'car')
519    if key in self.keyModifiers.keys():
520     
521      # just remove the key modifier preserving the alternative values
522      if mode in self.keyModifiers[key]['modes'].keys():
523        # get the previous value
524        options = self.m.get('options', None)
525        # remeber the old value, if not se use default from options
526        # if available
527        if options:
528          defaultValue = options.getKeyDefault(key, None)
529        else:
530          defaultValue = None
531        oldValue = self.get(key,defaultValue)
532        del self.keyModifiers[key]['modes'][mode]
533        # was this the last key ?
534        if len(self.keyModifiers[key]['modes']) == 0:
535          # no modes registered - unregister from modifiers
536          # TODO: handle non-mode modifiers in the future
537          del self.keyModifiers[key]
538        # notify watchers
539        self._notifyWatcher(key, oldValue)
540        # done
541        return True
542      else:
543        print("modrana: can't remove modifier that is not present")
544        print("key: %s, mode: %s" % (key, mode))
545        return False
546    else:
547      print("modRana: key %s has no modifier and thus cannot be removed" % key)
548      return False
549
550  def hasKeyModifier(self, key):
551    """return if a key has a key modifier"""
552    return key in self.keyModifiers.keys()
553
554  def hasKeyModifierInMode(self, key, mode=None):
555    """return if a key has a key modifier"""
556    if mode == None:
557      mode = self.d.get('mode', 'car')
558    if key in self.keyModifiers.keys():
559      return mode in self.keyModifiers[key]['modes'].keys()
560    else:
561      return False
562
563  def getModes(self):
564    modes = {
565      'cycle':'Cycle',
566      'walk':'Foot',
567      'car':'Car',
568      'train':'Train',
569      'bus':'Bus',
570    }
571    return modes
572
573  def getModeLabel(self, modeName):
574    "get a label for a given mode"
575    try:
576      return self.getModes()[modeName]
577    except KeyError:
578      print('modrana: mode %s does not exist and thus has no label' % modeName)
579      return None
580
581  def _updateCenteringShiftCB(self, key=None, oldValue=None, newValue=None):
582    """update shifted centering amount
583
584    this method is called if posShiftAmount or posShiftDirection
585    are set and also once at startup"""
586    # get the needed values
587    # NOTE: some of them might have been updated just now
588    (sx,sy,sw,sh) = self.get('viewport')
589    shiftAmount = self.d.get('posShiftAmount', 0.75)
590    shiftDirection = self.d.get('posShiftDirection', "down")
591    scale = int(self.get('mapScale', 1))
592
593    x=0
594    y=0
595    floatShiftAmount = float(shiftAmount)
596    """this value might show up as string, so we convert it to float, just to be sure"""
597
598    if shiftDirection:
599      if shiftDirection == "down":
600        y =  sh * 0.5 * floatShiftAmount
601      elif shiftDirection == "up":
602        y =  - sh * 0.5 * floatShiftAmount
603      elif shiftDirection == "left":
604        x =  - sw * 0.5 * floatShiftAmount
605      elif shiftDirection == "right":
606        x =  + sw * 0.5 * floatShiftAmount
607      """ we dont need to do anything if direction is set to don't shift (False)
608      - 0,0 will be used """
609    self.centerShift = (x,y)
610   
611    # update the viewport expansion variable   
612    tileSide = 256
613    mapTiles = self.m.get('mapTiles')
614    if mapTiles: # check the mapTiles for tile side length in pixels, if available
615      tileSide = mapTiles.tileSide
616    tileSide = tileSide * scale # apply any possible scaling
617    (centerX,centerY) = ((sw/2.0),(sh/2.0))
618    ulCenterDistance = simplePythagoreanDistance(0, 0, centerX, centerY)
619    centerLLdistance = simplePythagoreanDistance(centerX, centerY, sw, sh)
620    diagonal = max(ulCenterDistance, centerLLdistance)
621    add = int(math.ceil(float(diagonal)/tileSide))
622    self.expandViewportTiles = add
623
624  def _modeChangedCB(self, key=None, oldMode=None, newMode=None):
625    """handle mode change in regards to key modifiers and option key watchers"""
626    # get keys that have both a key modifier and a watcher
627    keys = filter(lambda x: x in self.keyModifiers.keys(), self.watches.keys())
628    """ filter out only those keys that have a modifier for the new mode or
629    had a modifier in the previous mode
630    otherwise their value would not change and thus triggering a watch is not necessary """
631    keys = filter(
632                  lambda x: newMode in self.keyModifiers[x]['modes'].keys() or oldMode in self.keyModifiers[x]['modes'].keys(),
633                  keys )
634    for key in keys:
635      # try to get some value if the old value is not available
636      options = self.m.get('options', None)
637      # remeber the old value, if not se use default from options
638      # if available
639      if options:
640        defaultValue = options.getKeyDefault(key, None)
641      else:
642        defaultValue = None
643      oldValue = self.get(key, defaultValue)
644      # notify watchers
645      self._notifyWatcher(key, oldValue)
646
647
648  def _removeNonPersistentOptions(self, inputDict):
649    """keys that begin with # are not saved
650    (as they mostly contain data that is either time sensitive or is
651    reloaded on startup)
652    ASSUMPTION: keys are strings of length>=1"""
653    try:
654      return dict((k, v) for k, v in inputDict.iteritems() if k[0] != '#')
655    except Exception, e:
656      print('options: error while filtering options\nsome nonpersistent keys might have been left in\nNOTE: keys should be strings of length>=1\n', e)
657      return self.d
658
659  def _saveOptions(self):
660    print("modRana: saving options")
661    try:
662      f = open(self.paths.getOptionsFilePath(), "w")
663      # remove keys marked as nonpersistent
664      self.d['keyModifiers'] = self.keyModifiers
665      d = self._removeNonPersistentOptions(self.d)
666      marshal.dump(d, f)
667      f.close()
668      print("modRana: options successfully saved")
669    except IOError:
670      print("modRana: Can't save options")
671    except Exception, e:
672      print("modRana: saving options failed:", e)
673
674  def _loadOptions(self):
675    print("modRana: loading options")
676    succcess = False
677    try:
678      f = open(self.paths.getOptionsFilePath(), "r")
679      newData = marshal.load(f)
680      f.close()
681      # TODO: check out if this is needed anymore
682      if 'tileFolder' in newData: #TODO: do this more elegantly
683        del newData['tileFolder']
684      if 'tracklogFolder' in newData: #TODO: do this more elegantly
685        del newData['tracklogFolder']
686      for k,v in newData.items():
687        self.set(k,v)
688      succcess = True
689    except Exception, e:
690      print("modRana: exception while loading saved options:\n%s" % e)
691      #TODO: a yes/no dialog for clearing (renaming with timestamp :) the corrupted options file (options.bin)
692      succcess = False
693
694    self.overrideOptions()
695    return succcess
696
697  def overrideOptions(self):
698    """
699    without this, there would not be any projection values at start,
700    because modRana does not know, what part of the map to show
701    """
702    self.set('centred', True) # set centering to True at start to get setView to run
703    self.set('editBatchMenuActive', False)
704
705  ## PROFILE PATH ##
706
707  def getProfilePath(self):
708    """return the profile folder (create it if it does not exist)
709    NOTE: this function is provided here in the main class as some
710    ordinary modRana modules need to know the profile folder path before the
711    option module that normally handles it is fully initialized
712    (for example the config module might need to copy default
713    configuration files to the profile folder in its init)
714    """
715    # get the path
716    modRanaProfileFolderName = '.modrana'
717    userHomePath = os.getenv("HOME")
718    profileFolderPath = os.path.join(userHomePath, modRanaProfileFolderName)
719    # make sure it exists
720    createFolderPath(profileFolderPath)
721    # return it
722    return profileFolderPath
723
724  ## MAP LAYERS ##
725  """map layer information is important and needed by many modules during their initialization,
726  so it is handled here"""
727  def getMapLayers(self):
728    return self.configs.getMapLayers()
729
730  ## STARTUP TIMING ##
731
732  def addTime(self, message):
733    timestamp = time.time()
734    self.timing.append((message,timestamp))
735    return (timestamp)
736
737  def addCustomTime(self, message, timestamp):
738    self.timing.append((message,timestamp))
739    return (timestamp)
740
741  def reportStartupTime(self):
742    if self.timing:
743      print("** modRana startup timing **")
744
745      # print device identificator and name
746      if self.dmod:
747        deviceName = self.dmod.getDeviceName()
748        deviceString = self.dmod.getDeviceIDString()
749        print("# device: %s (%s)" % (deviceName, deviceString))
750
751      tl = self.timing
752      startupTime = tl[0][1] * 1000
753      lastTime = startupTime
754      totalTime = (tl[-1][1] * 1000) - startupTime
755      for i in tl:
756        (message, t) = i
757        t = 1000 * t # convert to ms
758        timeSpent = t - lastTime
759        timeSinceStart = t - startupTime
760        print( "* %s (%1.0f ms), %1.0f/%1.0f ms" % (message, timeSpent, timeSinceStart, totalTime))
761        lastTime = t
762      print("** whole startup: %1.0f ms **" % totalTime)
763    else:
764      print("* timing list empty *")
765
766if __name__ == "__main__":
767
768  print(" == modRana Starting == ")
769
770  program = ModRana()
Note: See TracBrowser for help on using the repository browser.