LAST PAGE  BACK TO INDEX  NEXT PAGE

[19.0] Some Worked Examples (3)

[19.0] Some Worked Examples (3)

v5.0 / 19 of 23 / 01 sep 99 / gvg

* Still more interesting VEE examples!


[19.1] DISPLAYING SEQUENTIAL TRACES
[19.2] AN ALARM SYSTEM
[19.3] DRAGGING & DROPPING PANELS

 BACK TO INDEX

[19.1] DISPLAYING SEQUENTIAL TRACES

* One of our people in Singapore had a customer who wanted to display a sequence of waveforms on a single display, adding them one at a time. I wrote an example program for him that will display up to three waveforms, but could easily be redesigned to handle more.

The problem is that an XY Trace object cannot be dynamically configured to display different numbers of traces -- you set it up to display three traces, it must be given three waveforms. The trick to make it display different numbers of waveforms -- one, two, or three -- is to provide additional dummy waveforms that simply show up as a straight line in order to ensure that the total number of waveforms given to the XY Trace object is always three.

The unused traces show up as a colored line in the middle of the display, but this is not much of a nuisance (it's not so different from what you would get on a multi-channel oscilloscope display). The user can specify an offset to allow the traces to be separated (or not) on the display.


    +-----------------------------------------------+
    | Main                                          |
    +-----------------------------------------------+
    |   +--------+                                  |
    |   | OffSet |                                  |
    |   +--------+                                  |
    |   | 2      |                   [ New Trace ]  |
    |   +--------+                                  |
    | +-------------------------------------------+ |
    | |                  XY Trace                 | |
    | +-------------------------------------------+ |
    | |                                           | |
    | |                                  x        | |
    | | x    x x               x x     x   x  x   | |
    | |  x  x    x     x x   x     x x      x   x | |
    | |    x       x x     x                      | |
    | |                                           | |
    | |                                           | |
    | |  +         +  +           +            +  | |
    | | + +      +     +  + +   +   + +    + +    | |
    | |     + +               +        + +        | |
    | |                                           | |
    | |                                           | |
    | |    *               * *           *    * * | |
    | |  *   *    *  *        *        *  * *     | |
    | | *      *        *       * *  *            | |
    | |                             *             | |
    | +-------------------------------------------+ |
    |                    [ Exit ]                   |
    +-----------------------------------------------+
 
There are a few restrictions in this example, however. First, you can only add waveforms; you cannot subtract them. Second, all the waveforms must have the same number of points (though this is likely to be the case in practice anyway).

The implementation is a little tricky. I wasn't able to think of a good way to store the different waveforms in a single data structure, so each gets its own global variable. Another tricky feature is that the routines don't know how big the waveforms are going to be until they get the first one.

The program is based on three UserFunctions:

In practice, all the user has to do is specify the OffSet Value and initialize the number of traces (Ntrace) to 0, and then call ShowTrace to display the traces:


     +---------+    +---------------+
     | OffSet  |    | Set Variable  |
     +---------+    +---------------+
     | 0       +--->|     OffSet    |
     +---------+    +-------+-------+
                            |
     +---------+    +-------+-------+
     | Integer |    | Set Variable  |
     +---------+    +---------------+
     | 0       +--->|     Ntrace    |
     +---------+    +-------+-------+
                            |
                        +---+---+
                        | Until |
                        | Break +---+
                        +---+---+   |
                            |       |
                        +---+---+   |
                     OK | Exit  |   |
                        +-------+   |
                                    |
                              +-----+-----+
                           OK | New Trace +--+
                              +-----------+  |
                                             |
                                       +-----+-----+
                                       |   Noise   |
                                       | Generator +--+
                                       +-----------+  |
                                                      |
      +-----------------------------------------------+
      |
      |                                    +-------------------------
      |                                    |                XY Trace
      |                                    +--------+----------------
      |                                    |        |
      |   +----------------+-------------->| Trace1 |
      +-->| Call Showtrace +--------+      |        |
          +----------------+---+    |      |        |
                               |    |      |        |
                               |    +----->| Trace2 |
                               |           |        |
                               |           |        |
                               |           |        |
                               +---------->| Trace3 |
                                           |        |
                                           |        |
                                      +--->| Auto   |
                                      |    | Scale  |
                                      |    |        |
                                      |    +--------+--------------+--
                                      |                            | 
                                      +----------------------------+
 
ShowTrace in turn will determine if no traces have been displayed -- and call InitTrace -- or if there have been other traces displayed -- and call AddTrace. It then returns the values of all three of the waveform global variables:

         +---------------------------------------------+
         |                  Formula                    |
         +---------------------------------------------+
    A -->|( Ntrace == 0 ? InitTrace(A) : AddTrace(A) ) |
         +---------------------+-----------------------+
                               |
                       +-------+-------+
                       | Get Variable  |   USERFUNCTION SHOWTRACE
                       +---------------+
                       |    Trace1     +----------------------> Trace1
                       +-------+-------+
                               |
                       +-------+-------+
                       | Get Variable  |
                       +---------------+
                       |    Trace2     +----------------------> Trace2
                       +-------+-------+
                               |
                       +-------+-------+
                       | Get Variable  |
                       +---------------+
                       |    Trace3     +----------------------> Trace3
                       +---------------+
 
InitTrace loads the initial waveform into its global variable, increments Ntrace, then walks through a count to initialize the other two global variables to dummy waveforms:

                                               +--------------+
                   USERFUNCTION INIT           | Set Variable |
                                               +--------------+
         +------------------------------------>|    Trace1    |
         |                                     +-------+------+
         |                                             |
         |                                     +-------+------+
         |                                     |   Formula    |
  wave --+----------+                          +--------------+
         |          |                          | Ntrace = 1   |       
         |  +-------+-------+                  +--------------+
         |  | For Count: 2  +---+    
         |  +---------------+   |      +----------------------+
         |                      |      |        Formula       |
         |  +---------+---------+      +----------------------+    build
         |  |         |            +-->| ramp(totsize(A),0,0) +--+ dummy 
         |  |         |            |   +----------------------+  | data
         |  |         |            |                             |
         |  |         |            |  +--------------------------+
         |  |         |            |  |                           
         |  |   +-----+----+ array |  |   +----------+
         +--|-->| UnBuild  +-------+  +-->|  Build   +--+ dummy
            |   | Waveform +------------->| Waveform |  | waveform
            |   +----------+ time         +----------+  |
            |                                           |
            |   +---------------------------------------+
            |   |
            |   |  +-----------------+      +--------------+
            |   |  |  Demultiplexer  |      | Set Variable |
            |   |  +------+-+--------+      +--------------+
            |   +->| Data | | Addr 0 +----->|    Trace2    |
            +----->| Addr | | Addr 1 +--+   +--------------+
                   +------+-+--------+  |
                                        |   +--------------+
                                        |   | Set Variable |
                                        |   +--------------+
                                        +-->|    Trace3    |
                                            +--------------+
 
AddTrace routes the waveform (with the OffSet added) to its appropriate global variable and increments Ntrace:

              +---------------------+        USERFUNCTION ADDTRACE
              |       Formula       |
              +---------------------+   
          +-->| A + OffSet * Ntrace +--+ input waveform plus offset
          |   +---------------------+  |
          |                            |
          |                   +--------+
          |                   |
          |                   |  +---------------+      +--------------+
          |   +------------+  |  | Demultiplexer |      | Set Variable |
          |   |  Formula   |  |  +------+--------+      +--------------+
          |   +------------+  +->| Data | Addr 0 +----->|    Trace2    |
  wave ---+-->| Ntrace - 1 +---->| Addr | Addr 1 +--+   +--------------+
              +------------+     +------+--------+  |
            specify trace to set        |           |   +--------------+
                                        |           |   | Set Variable |
                                        |           |   +--------------+
                                        |           +-->|    Trace3    |
                                        |               +--------------+
                             +----------+----------+
                             |       Formula       |
                             +---------------------+
                             | Ntrace = Ntrace + 1 | increment trace count
                             +---------------------+
 
Note that this example doesn't have provisions for clearing the traces or preventing the user from adding one trace too many. Such features would have made the program harder to understand but can be easily added by the user.

This example program is available as xmtrace.vee.

 TOP OF PAGE

[19.2] AN ALARM SYSTEM

* A user wanted to know how he could build an alarm system in HP VEE that would allow him to specify a time at which VEE would take a specific action. The alarm might also occur only once; or every day; or only on the weekdays; and so on.

So I wrote a small sample program to show the basic principles. Being an example, the program avoids such issues as how the user sets the alarm in the first place (which is another programming exercise in itself). It requires setting the alarm time in military format. If the alarm time for the day has already been passed, then the alarm is set for the following day.

The program is built around two Global variables:

The main part of the program appears as follows:


   +---------+
   |  Hours  | hours in military time
   +---------+
   | 14      +--+                       +------------+
   +---------+  |                       | Set Global |
                +-->+---------------+   +------------+
   +---------+      | Call TimeInit +-->|   ATime    | set alarm time
   | Minutes |  +-->+-------+-------+   +------------+
   +---------+  |           |
   | 11      +--+    +------+-----+
   +---------+       | Set Global | 
                     +------------+
   +---------+   +-->|   ASched   | set scheduling 
   | Integer |   |   +------+-----+
   +---------+   |          |
   | 0       +---+          |
   +---------+      +-------+------+ 
                    | On Cycle: 60 +--+-------+
                    +--------------+  |       |
                                      |  +----+----+   +----------------+
               +----------------------+  | Formula |   |  AlphaNumeric  |
               |                         +---------+   +----------------+
   +-----------+-----------+             |  Atime  +-->|                |
   |      If/Then/Else     |             +---------+   +----------------+
   +----------------+------+                display current alarm time
   | now() >= ATime | Then +--+ fire
   |                | Else |  | alarm
   +----------------+------+  |
                              |
               +--------------+
               |
   +-----------+-----------+
   |      If/Then/Else     | handle scheduling
   +-------------+---------+
   | Asched==0   | Then    +--------> handle single alarm
   | Asched==1   | Else If +-----> handle daily alarm
   |             | Else    +--> handle weekday alarm
   +-------------+---------+   
 
The program begins by setting the alarm time (using the TimeInit() function, more on this later) and the type of alarm scheduling, then goes into a loop run by an On Cycle object that fires every 60 seconds and drives an If/Then/Else object.

The If/Then/Else object in turn checks to see if the current time exceeds the alarm time, and if so fires its output. This drives a second If/Then/Else object that checks to see what sort of scheduling was specified and then executes the proper code to do the job.

Note that the current alarm time is displayed whenever the On Cycle object is fired. This is a useful debugging tool to ensure that the alarm is properly updated.

Handling a single alarm is easy: the program displays a message box to indicate an alarm (in a practical program this would be something a little more useful, but this is an example) and then does a Break to get out of the On Cycle loop.


   handle single alarm --------+
                               |
                        +------+------+
                        | Message Box |
                        +------+------+
                               |
                           +---+---+
                           | Break |
                           +-------+
 
Handling a daily alarm is a little trickier. Before popping up the message box, the program first adds a day's worth of seconds to the alarm time to set the alarm for the next day:

   handle daily alarm ------------+
                                  |
    +------------------+   +------+------+
    |     Formula      |   | Set Global  |
    +------------------+   +-------------+
    | Atime + 24*60*60 +-->|    ATime    | update alarm 24 hours
    +------------------+   +------+------+
                                  | 
                           +------+------+
                           | Message Box |
                           +------+------+
                                  |
                              +---+---+
                              | Next  |
                              +-------+
 
Any other time increment could be added as well. Note that a Next is used to start the cycle all over again for the next day.

Handling a weekday alarm is similar, but adds an If/Then/Else object to check to see what day of the week it is, using the wday() function:


   handle weekday alarm ----------+
                                  |
    +------------------+   +------+------+
    |     Formula      |   | Set Global  |
    +------------------+   +-------------+
    | Atime + 24*60*60 +-->|    ATime    | update alarm 24 hours
    +------------------+   +------+------+
                                  | 
                      +-----------+-----------+
                      |      If/Then/Else     | fire if not day 5 or 6
                      +----------------+------+
                      | wday(now())<5  | Then +---------+
                      |                | Else |         |
                      +-----------+----+------+  +------+------+
                                  |              | Message Box |
                              +---+---+          +-------------+
                              | Next  |
                              +-------+
 
Note that the Next object is connected to the If/Then/Else to ensure the cycle is repeated whether the Message Box is executed or not.

Other scheduling schemes could be added simply by increasing the number of values in ASched, adding extra branches to the If/Then/Else object, and adding the approriate logic to each branch.

* The main program is straightforward, but the TimeInit() function is a little tricky. It has the layout:


	   +-------------------------------------------+
	   |                  Formula                  |
	   +---+---------------------------------------+
   h ----->| H | hmsToSec(H MOD 24, M, 0) + TimeAdjust +--+
       +-->| M |                                       |  |
       |   +---+------------------+--------------------+  |
       |                          |                       |
       |       +------------------+                       |
       |       |         +--------------------------------+ 
       |       |         |
       |       |         |   +------------------------------+
   m --+       |         |   |           Formula            |
               |         |   +---+--------------------------+
           +---+---+     +-->| T | (N-(N MOD (24*60*60) + T +--+
           | now() +--+----->| N |                          |  |
           +-------+  |      +---+--------------------------+  |
		      |                                        |
		      |  +-------------------------------------+
		      |  |
                      |  |   +------------------------------+
                      |  |   |           Formula            |
                      |  |   +---+--------------------------+
                      |  +-->| T | (T>N ? T : (T+24*60*60)  +-------> X
                      +----->| N |                          |   
                             +---+--------------------------+   
 
This UserFunction accepts the hour and minute values specified and returns the absolute machine time when the alarm will occur. The Formula at top converts the hours and minutes specified by the user into a value in seconds; there are two little tricks added:

The Formula in the middle adds the time returned by the Formula at top (which gives the number of seconds into the day that the alarm will occur) and adds the value of now() at the previous midnight to give an absolute time for comparisons.

Finally, the Formula at the bottom checks to see if the alarm time has already passed, and if so adds another day's time, on the assumption that if somebody entered an alarm time of 10:00 when it was already 13:33, the user probably meant for the alarm to occur the next day.

* The TimeAdjust() function is something of a kluge:


    +------------------------------+
    |           To String          |
    +------------------------------+
    | WRITE TEXT 0 TIME:HM:H24 EOL +--+
    |                              |  |
    +------------------------------+  |
				      |
             +------------------------+
	     |
	     |   +--------------------------------------------+
	     |   |                  Formula                   |
	     |   +--------------------------------------------+
	     +-->| (asInt32(A)==0 ? 0 : (24-asInt32(A))*3600) +--> X
		 +--------------------------------------------+
 
The To String object generates a string giving the time it thinks corresponds to a value of 0. On the Greenwich meridian, this will be midnight, but it will decrement by an hour for each timezone going west.

The Formula then computes the appropriate correction factor in seconds for the computer's time zone. Note that a value of 0 is a special case and the Formula checks for it to prevent it from adding a 24 hour offset on the Greenwich meridian.

 TOP OF PAGE

[19.3] DRAGGING & DROPPING PANELS

* The HP VEE games are full of interesting features. One of them is that in some programs, like Solitaire, you can "drag and drop" panels displaying playing card icons and they will "snap" into a grid position.

The lab engineer who designed the games, Doug K, was nice enough to come up with an example program, xdrag.vee, that shows how this is done. This particular example program only uses a single icon, but the principles apply to implementations with more icons.

* The scheme is not entirely intuitive. It is based on three elements:

Using these three elements, the scheme can be implemented as follows:

The main program of this example has the layout:


   +-----------------+
   | Call Initialize |
   +--------+--------+
	    |
   +--------+--------+
   | Call ShowGrid   |
   +--------+--------+
	    |
        +---+---+
        | Until |
        | Break +---+
	+-------+   |
		    |
       +------------+------------+-->+-------------------------+
       | Call CheckPanelPosition |   | Call RespondToPanelMove |
       +-------------------------+-->+-------------------------+
 
This reflects the scheme outlined above:

In detail, the "Initialize()" UserFunction looks like this:


   +----------------------------------------------------------+
   | Initialize                                               |
   +----------------------------------------------------------+
   |                                                          |
   |  +------------------------+  +------------------------+  |
   |  |     Declare PanelX     |  |     Declare PanelY     |  |
   |  +------------------------+  +------------------------+  |
   |  | Name:     [  panelX  ] |  | Name:     [  panelY  ] |  |
   |  | Scope:    [  Global  ] |  | Scope:    [  Global  ] |  |
   |  | Type:     [  Int32   ] |  | Type:     [  Int32   ] |  |
   |  | Num Dims: [    0     ] |  | Num Dims: [    0     ] |  |
   |  +------------------------+  +------------------------+  |
   |                                                          |
   |  +----------------------------------+                    |
   |  |             Formula              |                    |
   |  +----------------------------------+                    |
   |  | panelX = 150                     |                    |
   |  | panelY = 150                     |                    |
   |  | showPanel("Panel",panelX,panelY) |                    |
   |  +----------------------------------+                    |
   |                                                          |
   +----------------------------------------------------------+
 
Not much to it, it just declares the global variables "panelX" and panelY", initializes them, and then uses "ShowPanel()" to display the panel at the initial position.

The "ShowGrid()" UserFunction sets up display cosmetics. Although this is important, it is a distraction from the discussion of how the program handles the movement of the panel icon, and so we'll discuss the details of "ShowGrid()" later.

The "CheckPanelPosition()" UserFunction looks like this:


   +-----------------------------------------------------------------------+
   | CheckPanelPosition                                                    |
   +------------------------------------------------------------------+----+
   |                                                                  |    |
   |                                                      +------+    |    |
   |  +------------------------+                          |      |    |    |
   |  |         Formula        |   +---------+         +--+---+  |    |    | 
   |  +------------------------+   | Unbuild +--+----->| Gate +--|--->|newX|
   |  | panelPosition("panel") +-->| Coord   +--|-+    +------+  |    |    |
   |  +------------------------+   +---------+  | |              |    |    |
   |                                            | |       +------+    |    |
   |                                            | |       |      |    |    |
   |   +----------------------------------------+ |    +--+---+  |    |    |
   |   | +----------------------------------------+--->| Gate +--|--->|newY|
   |   | |                                             +------+  |    |    |
   |   | |                                                       |    |    |
   |   | |   +-------------------------------------+             |    |    |
   |   | |   |            If/Then/Else             |             |    |    |
   |   | |   +---+--------------------------+------+             |    |    |
   |   +-|-->| X |[ x!=panelX OR y!=panelY ]| then +-------------+    |    |
   |     +-->| Y |                          | else |                  |    |
   |         +---+--------------------------+------+                  |    |
   |                                                                  |    |
   +------------------------------------------------------------------+----+
 
Again, it's straightforward. It uses "panelPosition()" to poll "panel()" for its position, then takes the coordinate positions returned and sends then to an If/Then/Else box.

The If/Then/Else box compares them to the coordinates stored in "panelX" and "panelY". If either has changed, the If/Then/Else box fires a pair of Gate objects to route the new values out of the output pins of the UserFunctions. If the position hasn't changed, "CheckPanelPosition()" just checks again.


   +---------------------------------------------------------------+
   | RespondToPanelMove                                            |
   +----+----------------------------------------------------------+
   |    |                                                          |
   |    |   +---------------------------+                          |
   |    |   |          Formula          |                          |
   |    |   +---+-----------------------+                          |
   |newX+-->| X | floor((x+37.5)/75)*75 +--+                       |
   |    |   +---+-----------------------+  |                       |
   |    |                                  |                       |
   |    |     +----------------------------+                       |
   |    |     |                                                    |
   |    |     |   +---------------------------------------------+  |
   |    |     |   |                    Formula                  |  |
   |    |     |   +----------+----------------------------------+  |
   |    |     +-->| snappedy | panelX=snappedX;                 |  |
   |    |         |          | panelY=snappedY;                 |  |
   |    |     +-->| snappedy | showPanel("panel",panelX,panelY) |  |
   |    |     |   +----------+----------------------------------+  |
   |    |     |                                                    |
   |    |     +----------------------------+                       |
   |    |                                  |                       |
   |    |   +---------------------------+  |                       |
   |    |   |          Formula          |  |                       |
   |    |   +---+-----------------------+  |                       |
   |newY+-->| Y | floor((x+37.5)/75)*75 +--+                       |
   |    |   +---+-----------------------+                          |
   |    |                                                          |
   +----+----------------------------------------------------------+
 
This UserFunction assumes that the snap grid spacing is 75 pixels. It determines which cell in the snap grid the center of the icon panel is located, using "floor()" function to round down, and multiplies the cell position by 75 to get the position to snap the icon panel to.

The new position is then stored in "panelX" and "panelY", and used by "showPanel()" to put the icon panel in its snap grid position.

* We bypassed the "ShowGrid()" UserFunction earlier. It has the layout:


   +----------------------------------------------------------------------+
   | ShowGrid                                                             |
   +----------------------------------------------------------------------+
   |                                                                      |
   |                     +-----------------------------------------+      |
   |                     |                 Formula                 |      |
   |  +--------------+   +-+---------------------------------------+      |
   |  | For Count: 7 +-->|i| showPanel("line0"+i, 0, i*75, 800, 1) |      |
   |  +--------------+   +-+---------------------------------------+      |
   |                                                                      |
   |                     +---------------------------------------------+  |
   |                     |                   Formula                   |  |
   |  +--------------+   +-+-------------------------------------------+  |
   |  | For Count: 9 +-->|i| showPanel("line1"+i, (i+1)*75, 0, 1, 500) |  |
   |  +--------------+   +-+-------------------------------------------+  |
   |                                                                      |
   +----------------------------------------------------------------------+
 
The grid in the program's panel view consists of 9 lines across and 7 lines from top to bottom. Since you can't draw lines on the panel view as such, the program uses a set of UserFunctions named "line00()" through "line06()" and "line10()" though "line18" to create the lines.

All these UserFunctions consist of is a black panel view. The "ShowGrid()" UserFunction lays them down with the appropriate length and a width of 1 pixel in the proper locations. Since there can only be one visible panel view per UserFunction, there has to be a separate UserFunction for each line.

A bitmap panel could be used as well, but the advantage of this approach is that the grid spacing can be modified programmatically. A bitmap would require rebuilding the bitmap every time the grid spacing changes.

* To implement this scheme for multiple icons, you need to maintain global variables that stores a list of X and Y positions, and also poll through a list of panel positions to see if any have moved. Implementing such a more complicated example is left as an exercise for the reader. Consulting the "Solitaire" example program would be a good place to start.

 TOP OF PAGE


 LAST PAGE  BACK TO INDEX  NEXT PAGE