[Home]GladeVCPprogramming

LinuxCNCKnowledgeBase | RecentChanges | PageIndex | Preferences | LinuxCNC.org

Showing revision 33

This page is deprecated

please refer to updated documentation in http://www.linuxcnc.org/docs/2.5/html/gui/gladevcp.html

Writing a GladeVCP application

This section is an overview of GladeVCP which is in emc2 master since of Dec 14,2010.

See also the related HalWidgets and ActionWidgets pages.

Design overview: from PyVCP to GladeVCP

Stock gladevcp can be used pretty much in the style of PyVCP - the major difference being that in the case of gladevcp, the glade user interface editor is used to design a screen layout, whereas PyVCP is driven by manually edited XML files. Also, PyVCP uses the TkInter widgets, whereas gladevcp uses the much richer GTK toolkit. Both support 'HAL widgets' - widgets with one or more associated HAL pin which is the interface to the rest of EMC. Stock gladevcp and PyVCP panels are really just a bunch of virtual switches, dials, leds an so forth, wired to the outside world through the HAL layer, designed to set and read ints, bits and floats values.

Most widget sets, and their associated user interface editors, support the concept of callbacks - functions in user-writen code which are executed when 'something happens' in the UI - events like mouse clicks, characters typed, mouse movement, timer events, window hiding and exposure and so forth. Both PyVCP and stock gladevcp mainly do two things: mapping events on HAL widgets to actions like a value change of the associated HAL pin, and in the other direction - detecting if HAL pin values changed and updating the associated widget, like a LED, a meter, a bar, or just some label displaying a value. However, neither PyVCP nor stock gladevcp provide support for other actions than changing HAL values. Doing something more complex, like executing MDI commands to call a G-code subroutine, is outside scope.

Extending GladeVCP to support user-defined actions

As most of the infrastructure to support user-defined actions in glade and the GTK widget set is in place, the new gladevcp version really just provides a way for the user to write a Python module whose class methods - or in the simple case, just functions - can be referred to in glade as event handlers, a way to import this module(s) into gladevcp at runtime and properly link it with the rest of the HAL layer. Moreover, it comes with two categories of widgets - HAL widgets to interface to the HAL layer, and EMC Actions to execute predefined commands.

So there are now two ways to add actions to a GladeVCP panel: either use some stock EMC Action Widgets, or do it "by hand" - writing a Python handler and associate it with a widget event like 'pressed' or 'value-changed'.

Using Action widgets

The EMC Actions section in glade includes several Action widgets, which package typical actions in an easy-to-use form. Actions are canned commands which can be associated with Buttons, Menu entries, and other widgets; when the underlying widget is activated, the canned command is executed. The Action widgets cover many state-related functions like handling E-Stop, Power, Run, Step, Pause, Resume, but also the capability to execute MDI commands - either G-Code one-liners, or call on O-word subroutines. In the case of EMC MDI Action widgets, it's possible to pass parameters to the MDI command which are taken from the current values of other HAL widgets and pins. See the ActionWidgets page for details. Note that with EMC Actions it's possible to create applications exclusively within glade - and without writing any Python code.

Using Python event handlers

If predefined Actions do not cover your needs, there's a way to associate a widget event with an arbitrary Python class method or function. This lower level of programming gives extensive control over what's happening in your application, and it's also more complex to write. Generally, the style is one of event-driven programming - gladevcp covers the main loop, and if "something happens", an event handler will be called.

Using EMC Stat to deal with status changes

Many actions are dependent on EMC status - is it in manual, MDI or auto mode? is a program running, paused or idle? You cannot start an MDI command while a G-code program is running, so this needs to be taken care of. Many EMC actions take care of this themselves, and related buttons and menu entries are deactivated when the operation is currently impossible.

When using Python event handlers - which are at a lower level than Actions - one needs to take care of dealing with status oneself. For this purpose, there's the EMC Stat widget - it's purpose is to associate event handlers with EMC status changes. The corresponding signal names are are:

Preparing for an MDI Action, and cleaning up afterwards

The EMC G-Code interpreter has a single set of variables, like feed, speed, relative/absolute mode and many more. Many O-word subroutines might change some of that mode - so the effect would be: you push a button, and suddenly the feed or some other variable has changed. To fix this surprising and undesirable side effect of a given O-word subroutine or G-code statement executed with an EMC ToggleAction_MDI, you might associate pre-MDI and post-MDI handlers with a given EMC ToggleAction_MDI. These handlers are optional and provide a way to save any state before executing the MDI Action, and to restore it to previous values. The signal names are mdi-command-start and mdi-command-stop; the handler names can be set in glade like any other handler.

HAL value change events

We also extended the way the HAL input pins interact with the gladevcp panel. Beyond HAL widgets displaying pin values, there is now a way to attach a 'value-changed' callback to a HAL pin, which fits nicely with the event-driven structure of a typical widget application: every activity, be it mouse click, key, timer expired, or the change of a HAL pin's value, generates a callback and is handled by the same orthogonal mechanism.

Note that the above refers to explictely declared HAL pins. HAL widgets come with a pre-defined signal 'hal-pin-changed', see the HalWidgets page for details.

See the 'Adding HAL pins' section below for details.

Persistent values in GladeVCP

A annoying aspect of gladevcp in its earlier form and pyvcp is the fact that you may change values through text entry, sliders, spin boxes, toggle buttons etc, but their settings are not saved and restored at the next run of EMC - they start at the default value as set in the panel or widget definition. Therefore I added an easy-to-use mechanism to save and restore the state of HAL widgets, and program variables (in fact instance attributes) as well. This mechanism uses the popular '.ini' file syntax and has safeguards against the .ini file and the corresponding user interface or program variables getting out of sync - just imagine renaming, adding or deleting widgets in glade: an .ini file lying around from a previous program version, or an entirely different user interface, would be not be able to restore the state properly. This situation is detected through a signature which depends on all object names and types which are saved and to be restored. In the case of signature mismatch, a new .ini file with default settings is generated.

Programming model

The overall protocol is as follows:

The simple handler model

For simple tasks it's sufficient to define functions named after the glade signal handlers. These will be called when the corresponding event happens in the widget tree. Here's a trivial example - it assumes that the 'pressed' signal of a GTK Button or HAL Button is linked to a callback called 'on_button_press':

 nhits = 0

 def on_button_press(gtkobj,data=None):
    global nhits
    nhits += 1
    gtkobj.set_label("hits: %d" % nhits)

Add this function to a Python file and run as follows:

gladevcp -u <myhandler>.py mygui.ui

Note communication between handlers has to go through global variables, which does not scale well and is positively Unpythonic. This is why we came up with the class-based handler model.

The class-based handler model

The idea here is: handlers are linked to class methods. The underlying class(es) are instantiated and inspected during gladevcp startup and linked to the widget tree as signal handlers. So the task now is to write:

Here is a minimum user-defined handler example module:

 Class MyCallbacks :
    def on_this_signal(self,obj,data=None):
        print "this_signal happened, obj=",obj

 def get_handlers(halcomp,builder,useropts):
    return [MyCallbacks ()] 

Now, 'on_this_signal' will be available as signal handler to your widget tree.

The get_handlers protocol

If during module inspection gladevcp finds a function 'get_handlers', it calls it as follows:

 get_handlers(halcomp,builder,useropts)
the arguments are:

gladevcp then inspects the list of class instances and retrieves their method names. Qualifying method names are connected to the widget tree as signal handlers. Only method names which do not begin with an '_' (underscore) are considered.

Note that regardless wether you're using the libglade or the new GtkBuilder? format for your glade UI, widgets can always be referred to as builder.get_object(<widgetname>). Also, the complete list of widgets is available as builder.get_objects() regardless of UI format.

Initialization sequence

It is important to know in which state of affairs your get_handlers() function is called so you know what is safe to do there and what not. First, modules are imported and initialized in command line order. After successful import, get_handlers() is called in the following state:

Once all modules have been imported and method names extracted, the following steps happen:

So when your handler class is initialized, all widgets are existent but not yet realized (displayed on screen). And the HAL component is'nt ready as well, so its unsafe to access pins values in your __init__() method.

If you want to have a callback to execute at program start after it is safe to access HAL pins, then a connect a handler to the realize signal of the top level window1 (which might be its only real purpose). At this point gladevcp is done with all setup tasks, the halfile has been run, and gladevcp is about to enter the gtk main loop.

Multiple callbacks with the same name

Within a class, method names must be unique. However, it is ok to have multiple class instances passed to gladevcp by get_handlers() with identically named methods. When the corresponding signal occurs, these methods will be called in definition order - module by module, and within a module, in the order class instances are returned by get_handlers().

The gladevcp -U <useropts> flag

Instead of extending gladevcp for any conceivable option which could potentially be useful for a handler class, you may use the -U <useroption> flag (repeatedly if you wish). This flag collects a list of <useroption> strings. This list is passed to the get_handlers() function (useropts argument). Your code is free to interpret these strings as you see fit. An possible usage would be to pass them to the Python exec function in your get_handlers() as follows:

 debug = 0
 ...
 def get_handlers(halcomp,builder,useropts):
    ...
    global debug # assuming there's a global var
    for cmd in useropts:
        exec cmd in globals()

This way you can pass arbitrary Python statements to your module through the gladevcp -U option, for example:

 gladevcp  -U debug=42 -U "print 'debug=%d' % debug" ...

This should set debug to 2 and confirm that your module actually did it.

Persistence

If you want any of: GTK widget state, HAL widgets output pin's values and/or class attributes of your handler class to be retained across invocations, proceed as follows:

 def __init__(self, halcomp,builder,useropts):
        self.halcomp = halcomp
        self.builder = builder
        self.useropts = useropts

        self.defaults = {   
             # the following names will be saved/restored as method attributes
             # the save/restore mechanism is strongly typed - the variable's type will be derived from the type of the
             # initialisation value. Currently supported types are: int, float, bool, string
             IniFile.vars : { 'nhits' : 0, 'a': 1.67, 'd': True ,'c' :  "a string"},

             # to save/restore all widget's state which might remotely make sense, add this:            
             IniFile.widgets :  widget_defaults(builder.get_objects()) 
             # a sensible alternative might be to retain only all HAL output widgets' state:
             # IniFile.widgets: widget_defaults(select_widgets(self.builder.get_objects(), hal_only=True,output_only = True)),
         }

and associate an .ini file with this descriptor:

        self.ini_filename = __name__ + '.ini'
        self.ini = IniFile(self.ini_filename,self.defaults,self.builder)
        self.ini.restore_state(self)

after restore_state(), self will have attributes set if as running the following:

        self.nhits = 0
        self.a = 1.67
        self.d = True
        self.c = "a string"

Note that types are saved and preserved on restore. This example assumes that the ini file didnt exist or had the default values from self.defaults.

After this incantation, you can use the following IniFil methods:

To save the widget and/or variable state on exit, connect a signal handler to the window1 (toplevel) destroy event:

 def on_destroy(self,obj,data=None):
     self.ini.save_state(self)

Next time you start the gladevcp application, the widgets should come up in the state when the application was closed.

Hand-editing .ini files

You can do that, but note that the values in self.defaults override your edits if there is a syntax or type error in your edit. The error is detected, a console message will hint about that happened, and the bad inifile will be renamed to have the .BAD suffix. Subsequent bad ini files overwrite earlier .BAD files.

Adding HAL pins

If you need HAL pins which are not associated with a specific HAL widget, add them as follows:

 import hal_glib
    ...
    # in your handler class __init__():
    self.example_trigger = hal_glib.GPin(halcomp.newpin('example-trigger',  hal.HAL_BIT, hal.HAL_IN))

To get a callback when this pin's value changes, associate a value-change callback with this pin, add:

    self.example_trigger.connect('value-changed', self._on_example_trigger_change)

and define a callback method (or function, in this case leave out the 'self' parameter):

    # note '_' - this method will not be visible to the widget tree
    def _on_example_trigger_change(self,pin,userdata=None):
        print "pin value changed to:" % (pin.get())

Adding timers

Since gladevcp uses GTK widgets which rely on the GObject base class, the full glib functionaliy is available. Here is an example for a timer callback:

    def _on_timer_tick(self,userdata=None):
        ...
        return True # to restart the timer;  return False for on-shot

        ...        
        # demonstrate a slow background timer - granularity is one second
        # for a faster timer (granularity 1msec), use this:
        # glib.timeout_add(100,  self._on_timer_tick,userdata)       # 10Hz
        glib.timeout_add_seconds(1, self._on_timer_tick)

Implementation note: Key handling in Axis

We believe key handling works OK, but since it is new code, we're telling about it you so you can watch out for problems; please let us know of errors or odd behaviour. This is the story:

Axis uses the TkInter? widget set. GladeVCP? applications use Gtk widgets and run in a separate process context. They are hooked into Axis with the Xembed protocol. This allows a child application like gladevcp to properly fit in a parent's window, and - in theory - have integrated event handling.

However, this assumes that both parent and child application properly support the Xembed protocol, which Gtk does, but TkInter? doesnt. A consequence of this is that certain keys would not be forwarded from a gladevcp panel to Axis properly under all circumstances. One of these situations was the case when an Entry, or SpinButton? widget had focus: in this case, for instance an Escape key would not have been forwarded to Axis and cause an abort as it should, with potentially disastrous consequences.

Therefore, key events in gladevcp are explicitly handled, and selectively forwarded to Axis, to assure that such situations cannot arise. For details, see the keyboard_forward() function in lib/python/gladevcp/xembed.py .

FAQ

Examples, and rolling your own gladevcp application

Visit emc2/configs/gladevcp for running examples. The templates subdirectory has starters for your own projects.

The probe example is actually cute and useful, at least for me. NB: it requires a running EMC.

Known Bugs

Change history


LinuxCNCKnowledgeBase | RecentChanges | PageIndex | Preferences | LinuxCNC.org
This page is read-only. Follow the BasicSteps to edit pages. | View other revisions | View current revision
Edited July 18, 2011 3:06 am by MichaelHaberler (diff)
Search:
Published under a Creative Commons License