PanelConfig.py
Go to the documentation of this file.
00001 ###############################################################################
00002 #
00003 # Package:   RoadNarrows Hekateros Robotic Manipulator Package
00004 #
00005 # Link:      https://github.com/roadnarrows-robotics/hekateros
00006 #
00007 # ROS Node:  hek_*
00008 #
00009 # File:      PanelConfig.py
00010 #
00011 ## \file 
00012 ##
00013 ## $LastChangedDate$
00014 ## $Rev$
00015 ##
00016 ## \brief Pan-Tilt panel configuration dialog and XML classes.
00017 ##
00018 ## \author Daniel Packard (daniel@roadnarrows.com)
00019 ## \author Robin Knight (robin.knight@roadnarrows.com)
00020 ##  
00021 ## \par Copyright:
00022 ##   (C) 2013-2014.  RoadNarrows LLC.\n
00023 ##   (http://www.roadnarrows.com)\n
00024 ##   All Rights Reserved
00025 ##
00026 # @EulaBegin@
00027 # @EulaEnd@
00028 #
00029 ###############################################################################
00030 
00031 import sys
00032 import os
00033 import time
00034 
00035 from Tkinter import *
00036 from Tkconstants import *
00037 from tkFileDialog import *
00038 import tkFont
00039 
00040 import xml.parsers.expat as expat
00041 
00042 from hekateros_control.Utils import *
00043 
00044 # ------------------------------------------------------------------------------
00045 # Class ConfigDlg
00046 # ------------------------------------------------------------------------------
00047 
00048 #
00049 ## \brief Pan-Tilt Panel configuration dialog.
00050 #
00051 class ConfigDlg(Toplevel):
00052   ## \brief User's home directory.
00053   Home = os.path.expanduser("~")
00054 
00055   ## \brief User-specific configuration directory (in home directory).
00056   UserDirName = ".roadnarrows"
00057 
00058   ## \brief hek_panel application configuration file name.
00059   ConfigFileName = "hek_panel.xml"
00060 
00061   PathNameDft = Home + os.path.sep + \
00062                 UserDirName + os.path.sep + \
00063                 ConfigFileName
00064 
00065   ## \brief Configuration default.
00066   ConfigDft = \
00067   {
00068     'warn_on_calib':    True, # do [not] warn user at calibration start
00069     'warn_on_release':  True, # do [not] warn user on release
00070     'force_recalib':    True, # do [not] force recalibration on all joints
00071   }
00072 
00073   #
00074   ## \brief Constructor.
00075   ##
00076   ## \param cnf     Configuration dictionary.
00077   ## \param kw      Keyword options.
00078   #
00079   def __init__(self, master=None, cnf={}, **kw):
00080 
00081     # initialize dialog data
00082     kw = self.initData(kw)
00083 
00084     Toplevel.__init__(self, master=master, cnf=cnf, **kw)
00085 
00086     self.title("hek_panel configuration")
00087 
00088     # create and show widgets
00089     self.createWidgets()
00090 
00091     # allows the enter button to fire either button's action
00092     self.m_bttnCancel.bind('<KeyPress-Return>', func=self.close)
00093 
00094     # center the dialog over parent panel
00095     if master is not None:
00096       self.update_idletasks()
00097       x0 = master.winfo_rootx()
00098       y0 = master.winfo_rooty()
00099       xp = x0 + (master.winfo_width() - self.winfo_width()) / 2 - 8
00100       yp = y0 + (master.winfo_height() - self.winfo_height()) / 2 - 20
00101       glist = [self.winfo_width(), self.winfo_height(), xp, yp]
00102       #self.withdraw() # hide the dialog until position and size is set
00103       self.geometry('{0}x{1}+{2}+{3}'.format(*glist))
00104       #self.deiconify() # now show
00105 
00106     # allows us to customize what happens when the close button is pressed
00107     self.protocol("WM_DELETE_WINDOW", self.close)
00108 
00109     #
00110     # Modal diagle settings.
00111     #
00112     # set the focus on dialog window (needed on Windows)
00113     self.focus_set()
00114 
00115     # make sure events only go to our dialog
00116     self.grab_set()
00117 
00118     # make sure dialog stays on top of its parent window (if needed)
00119     self.transient(master)
00120 
00121     # display the window and wait for it to close
00122     self.wait_window(self)
00123 
00124   #
00125   ## \brief Initialize class state data.
00126   ##
00127   ## \param kw      Keyword options.
00128   ##
00129   ## \return Modified keywords sans this specific class.
00130   ##
00131   def initData(self, kw):
00132     self.m_saved    = False
00133     self.m_filename = None
00134     if kw.has_key('config'):
00135       self.m_config = kw['config']
00136       del kw['config']
00137     else:
00138       self.m_config = ConfigDft;
00139     return kw
00140 
00141   #
00142   ## \brief Create gui widgets with supporting data and show.
00143   #
00144   def createWidgets(self):
00145     frame = Frame(self)
00146     frame.grid(row=0, column=0)
00147 
00148     # top heading
00149     w = Label(frame)
00150     helv = tkFont.Font(family="Helvetica",size=24,weight="bold")
00151     w['font']   = helv
00152     w['text']   = 'Configuration'
00153     w['anchor'] = CENTER
00154     w.grid(row=0, column=0, sticky=E+W)
00155 
00156     # warn on calibrate check button
00157     row = 1
00158     self.m_varWarnCalib = IntVar()
00159     self.m_varWarnCalib.set(self.m_config['warn_on_calib'])
00160     w = Checkbutton(frame,
00161         text="Show warning dialog before calibrating arm.",
00162         variable=self.m_varWarnCalib)
00163     w.grid(row=row, column=0, padx=5, sticky=W)
00164 
00165     # force recalibration on all joints check button
00166     row += 1
00167     self.m_varForceRecalib = IntVar()
00168     self.m_varForceRecalib.set(self.m_config['force_recalib'])
00169     w = Checkbutton(frame,
00170         text="Force (re)calibration for all joints on calibrate action.",
00171         variable=self.m_varForceRecalib)
00172     w.grid(row=row, column=0, padx=20, sticky=W)
00173 
00174     row += 1
00175     spacer = Label(frame, text="  ");
00176     spacer.grid(row=row, column=0, padx=5, sticky=W)
00177 
00178     # warn on release check button
00179     row += 1
00180     self.m_varWarnRelease = IntVar()
00181     self.m_varWarnRelease.set(self.m_config['warn_on_release'])
00182     w = Checkbutton(frame,
00183         text="Show warning dialog before releasing arm.",
00184         variable=self.m_varWarnRelease)
00185     w.grid(row=row, column=0, padx=5, sticky=W)
00186 
00187     row += 1
00188     spacer = Label(frame, text="  ");
00189     spacer.grid(row=row, column=0, padx=5, sticky=W)
00190 
00191     #
00192     # buttons
00193     #
00194     row += 1
00195     wframe = Frame(frame)
00196     wframe.grid(row=row, column=0)
00197 
00198     # cancel button
00199     w = Button(wframe, width=10, text='Cancel', command=self.close)
00200     w.grid(row=0, column=0, padx=2, pady=5)
00201     w['anchor']  = CENTER
00202     self.m_bttnCancel = w
00203 
00204     # save button
00205     w = Button(wframe, width=10, text='Save', command=self.ok)
00206     w.grid(row=0, column=1, padx=2, pady=5)
00207     w['anchor']  = CENTER
00208     self.m_bttnOk = w
00209 
00210   #
00211   ## \brief Destroy window callback.
00212   #
00213   def ok(self):
00214     if self.m_varWarnCalib.get():
00215       self.m_config['warn_on_calib'] = True
00216     else:
00217       self.m_config['warn_on_calib'] = False
00218     if self.m_varForceRecalib.get():
00219       self.m_config['force_recalib'] = True
00220     else:
00221       self.m_config['force_recalib'] = False
00222     if self.m_varWarnRelease.get():
00223       self.m_config['warn_on_release'] = True
00224     else:
00225       self.m_config['warn_on_release'] = False
00226     dirname = ConfigDlg.Home + os.path.sep + ConfigDlg.UserDirName
00227     if not os.path.isdir(dirname):
00228       try:
00229         os.mkdir(dirname, 0755)
00230       except OSError, err:
00231         print "%s: %s" % (dirname, err)
00232         return
00233     self.m_filename = dirname + os.path.sep + ConfigDlg.ConfigFileName
00234     xml = ConfigXml()
00235     xml.save(self.m_filename, self.m_config)
00236     self.close()
00237     self.m_saved = True
00238 
00239   #
00240   ## \brief Destroy window callback.
00241   #
00242   def close(self):
00243     self.destroy()
00244 
00245 
00246 # ------------------------------------------------------------------------------
00247 # Class ConfigXml
00248 # ------------------------------------------------------------------------------
00249 
00250 ##
00251 ## \brief Application hek_panel configuration xml class.
00252 ##
00253 class ConfigXml():
00254   def __init__(self):
00255     self.m_curData      = ""
00256     self.m_config       = None
00257 
00258   def parse(self, pathname=None):
00259     if pathname is None:
00260       pathname = ConfigDlg.PathNameDft;
00261     self.m_config = ConfigDlg.ConfigDft
00262     try:
00263       fp = open(pathname, 'r')
00264     except IOError, err:
00265       return self.m_config
00266     parser = expat.ParserCreate()
00267     parser.returns_unicode      = False
00268     parser.StartElementHandler  = self.onElemStart
00269     parser.CharacterDataHandler = self.onElemData
00270     parser.EndElementHandler    = self.onElemEnd
00271     self.m_stack = []
00272     try:
00273       parser.ParseFile(fp)
00274     except expat.ExpatError as e:
00275       print "%s: %s" % (pathname, expat.ErrorString(e.code))
00276     fp.close()
00277     return self.m_config
00278 
00279   def save(self, pathname, config):
00280     try:
00281       fp = open(pathname, 'w')
00282     except IOError, err:
00283       print "%s: %s." % (pathname, err)
00284       return
00285     fp.write("""\
00286 <?xml version="1.0" encoding="utf-8"?>
00287 <?xml-stylesheet type="text/xsl" href="http://roadnarrows.com/xml/Hekateros/1.0/hekateros.xsl"?>
00288 
00289 <!-- RoadNarrows Hekateros Top-Level Configuration -->
00290 <hekateros xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
00291   xsi:noNamespaceSchemaLocation="http://roadnarrows.com/xml/Hekateros/1.0/hekateros.xsd">
00292 
00293   <!-- hek_panel configuration -->
00294   <hek_panel>
00295 """)
00296 
00297     self.writeTree(fp, 4, config);
00298 
00299     fp.write("""\
00300   </hek_panel>
00301 
00302 </hekateros>
00303 """)
00304 
00305     fp.close()
00306 
00307   def writeTree(self, fp, indent, config):
00308     s = ' ' * indent
00309     for key,data in config.iteritems():
00310       if type(data) is dict:
00311         fp.write("{0}<{1}>\n".format(s, key))
00312         self.writeTree(fp, indent+2, data)
00313         fp.write("{0}</{1}>\n".format(s, key))
00314       else:
00315         fp.write("{0}<{1}>{2}</{3}>\n".format(s, key, config[key], key))
00316 
00317   def onElemStart(self, elem, attrs):
00318     #print "start-of-element", "<%s> attrs=%s" % (elem, repr(attrs))
00319     self.m_stack.append(elem)
00320     #print 'onElemStart', self.m_stack
00321     self.m_curData  = ""
00322 
00323   def onElemData(self, data):
00324     #print "char-data", repr(data)
00325     self.m_curData += data
00326 
00327   def onElemEnd(self, elem):
00328     #print "end-of-element", "<\%s>" % (elem)
00329     # <hekateros> <hek_panel> x
00330     if len(self.m_stack) == 3:
00331       elem = self.m_stack[2]
00332       if self.m_config.has_key(elem):
00333         if elem in ['warn_on_calib', 'force_recalib', 'warn_on_release']:
00334           self.m_config[elem] = self.cvtToBool(self.m_curData.strip())
00335     try:
00336       self.m_stack.pop()
00337     except:
00338       pass
00339     #print 'onElemEnd', self.m_stack
00340     self.m_curData  = ""
00341 
00342   def cvtToBool(self, data):
00343     if data.lower() == 'true':
00344       return True
00345     elif data.lower() == 'false':
00346       return False
00347     else:
00348       try:
00349         i = int(data)
00350         if i:
00351           return True
00352         else:
00353           return False
00354       except ValueError:
00355         return False
00356 
00357   def cvtToFloat(self, data):
00358     try:
00359       return float(data)
00360     except (TypeError, ValueError):
00361       return 0.0


hekateros_control
Author(s): Robin Knight , Daniel Packard
autogenerated on Mon Oct 6 2014 00:36:42