Control your PC with the 50g

Abstract

This article will demonstrate how to use your 50g to control your Windows/XP keyboard and mouse.  Examples include:

  1. Simple data entry.
  2. Multiple cell Excel data entry.
  3. A plotter application.
  4. Interactive mouse control.
  5. Controlling EMU48.

This automation is achieved with SerialKeys.  SerialKeys is a serial port keyboard and mouse interface primarily used as an accessibility feature.  Although this article targets the 50g and Windows/XP, it should be trivial to repurpose for any 48/49 and for OS/X and Linux.
 

Hardware

Windows/XP SerialKeys will only listen to COM ports 1 through 4 at speeds up to 19200 baud.  Although the 50g has USB and Serial connectivity I choose to use the Serial port (it was easier).

Hardware Inventory:

  1. 50g
  2. 50g Serial Cable (http://commerce.hpcalc.org/serialcable.php)
  3. 9 Pin Female Null Modem Adapter (http://commerce.hpcalc.org/nulladapter.php)
  4. USB to 9 Pin Serial Adapter (e.g. http://www.keyspan.com/products/usa19hs/).  NOTE:  USB Serial Adapter must be configured as COM port 1,2,3, or 4.  Refer to your documentation.
  5. Notebook with Windows/XP installed

Hook it all up (there is only one way to connect all the parts).

After you have it all connected, use HyperTerminal to confirm that you can communicate.  E.g. my USB Serial Adapter is configured as COM4: so I have HT configured like this:

To test, type from the 50g (you may want to backup IOPAR from your { HOME } directory first):

-33 CF
-78 SF
9600 BAUD
0 PARITY
"HELLO" XMIT

If working your Hyper Terminal session should look like this:

If you do not see anything, verify that you clicked the connect button (3rd from the left) and that everything is connected.  When done testing exit Hyper Terminal to free up your COM port.
 

SerialKeys Configuration

Enabling SerialKeys is very simple:

  1. Go to Control Panel
  2. Select Accessibility Options
  3. Select the General Tab
  4. Check "Use Serial Keys"
  5. Click Settings and select your COM port and Baud rate.  E.g.:

NOTE: As you disconnect and reconnect your USB Serial Adapter you may need to uncheck/Apply, and recheck/Apply "Use Serial Keys".

NOTE: SerialKeys can coexist with your standard keyboard and mouse.  No need to choose.
 

Sending text to your PC

All of the example programs can be obtained from http://sense.net/~egan/skb/IOKB.hp.  Just drop that object in to the root of your 50g.  All of the examples will be in the IOKB directory.

%%HP: T(3)A(R)F(.);
\<< DUP \-> s                 @ Copy first stack item and save to 's'
  \<<
    -33 CF                    @ Wire Transfers
    -78 SF                    @ Use Serial Port (not USB)
    PATH                      @ Put current PATH on stack
    IOPAR                     @ Put current IOPAR on stack
    9600 BAUD                 @ Set 9600 BAUD Rate
    0 PARITY                  @ Set 0 Parity
    s \->STR                  @ Convert to string
    XMIT                      @ Send out serial port
    DROP                      @ Ignore XMIT return code (bad practice)
    { HOME } EVAL             @ Stack should have 1:IOPARs, 2:PATH, cd to ( HOME )
    'IOPAR' STO               @ STO IOPARs as IOPAR
    EVAL                      @ PATH left on stack, EVAL to CD back
    -78 CF                    @ Use USB
  \>>
\>>

The above program (SKB) will backup IOPAR, setup and enable the serial port, transmit a string version of the first stack item (while leaving the stack unchanged), then will restore IOPAR and enable USB.  If you do not mind your flag or IOPAR settings changed, then only the following list of commands is required:

Setup (one time):

-33 CF -78 SF 9600 BAUD 0 PARITY

Send (as many times as you like):

->STR XMIT

To test SKB, open up Excel, put 123.45 on the stack and run SKB.

Ta da!  You will notice that the cursor is still in the same cell.  To actually skip from cell to cell we need to send special sequences.  Excel uses "Enter" to jump down and "Tab" to jump next.  With a few changes to SKB we can add this functionally.

In the IOKB directory there are two other programs; SKBE and SKBI, they function the same as SKB except terminate with an Enter or a Tab.

SKBE differences:

s \->STR 27 CHR + "enter." +

SKBI differences:

s \->STR 27 CHR + "tab." +

IANS, an ESC and the string "tab." or "enter." is appended to our input.  See [1] and [2] for more details.

To demonstrate SKBE and SKBI open a new Excel workbook and run the TT program.  The output should look like this:

The TT program generates a 10x10 times table using SKBE and SKBT to move from cell to cell.

%%HP: T(3)A(R)F(.);
\<<
  1 10 FOR i
    1 10 FOR j
      i j *
      j 10 == IF
      THEN
        SKBE            @ If at end of row press Enter
      ELSE
        SKBT            @ Not a end of row, Tab over
      END
      DROP
    NEXT
  NEXT
\>>

The SKB* commands have a lot of setup and teardown, the above example is probably not the most efficient way to do something like this.  The following examples below perform one setup and teardown with multiple SerialKey actions inbetween.
 

Use your PC as a 50g Plotter

The PPLOT program will plot a list of points and and connect them.  To generate some examples use the GENPLOY program (see Appendix A for source).  GENPOLY takes two arguments (radius and number of sides) and returns the points of a regular polygon.  The first point is returned twice, once at the start and one at the end of the list to close the polygon.

The following commands

100 3 GENPLOY PPLOT
100 4 GENPLOY PPLOT
100 5 GENPLOY PPLOT
100 6 GENPLOY PPLOT

produced the following output in Paint Brush:

A bit of help was required.  I did have to select the brush type, the color, and position the pointer away from the boarders.  It is possible to perform absolute addressing of the pointer, but I choose not to in this example.

Below is the PPLOT code with comments:

%%HP: T(3)A(R)F(.);
\<< 0 0 \-> p x y                                 @ Store list of points in p, zero x and y
  \<<
    -33 CF -78 SF PATH IOPAR 9600 BAUD 0 PARITY   @ Standard setup
    1 p SIZE FOR i                                @ Main loop processes each point
      p i GET                                     @ Get next point from list
      0 RND R\->I                                 @ Round and convert to integer type (i.e. pixels)
      DUP                                         @ Save this point to be the "last point" to
                                                  @   calculate movement
      i 1 \=/ IF
      THEN
        OBJ\-> DROP y - NEG SWAP x -              @ If not first point, calc movement from last
      ELSE
        OBJ\-> DROP NEG SWAP                      @ First point, just move there to start draw
      END
      DUP
      0 \>= IF                                    @ The following lines create a move to string
      THEN                                        @ consisting of a horizontal, vertical pair,
        "+"                                       @ each direction is prefixed with a + or -,
      ELSE                                        @ e.g. "+10,-10" = go ten right and ten up
        ""
      END
      SWAP + "," + SWAP
      DUP
      0 \>= IF
      THEN
        "+" SWAP +
      END
      + 27 CHR ",move," + SWAP + "." +            @ prepend "ESC,move," and append "."
      XMIT DROP                                   @ XMIT it, ignore return code
      i 1 == IF                                   @ After moving to the first point, hold down
      THEN                                        @ the left mouse button.  Future movements will
        27 CHR ",moulock,left." +                 @ draw.
        XMIT DROP
      END
      OBJ\-> DROP 'y' STO 'x' STO                 @ Remember that DUP at the start?  Use it for last
    NEXT                                          @   point
    27 CHR ",mourel." +                           @ All done, take finger off mouse button
    XMIT DROP
    { HOME } EVAL 'IOPAR' STO EVAL -78 CF         @ Back to USB mode
  \>>
\>>

Unfortunately there is no way to tell where your pointer is.  However there is a way to send to 0,0, then track your movements after that.  For more info see [1].
 

Interactive Mouse Control

The MOUSE program can move and click the mouse.  Use the arrows to move the mouse.  The + and - keys will double or half the number of pixels/movement.  Use left and right shift for the left and right mouse buttons (click only, no drag and drop, that is an exercise for the reader).  If you need a left double click use Enter.

%%HP: T(3)A(R)F(.);
\<< 0 20 0 \-> k s v          @ Init vars, s = pixes/move
  \<<
    -33 CF                    @ Wire Transfers
    -78 SF                    @ Use Serial Port (not USB)
    PATH                      @ Put current PATH on stack
    IOPAR                     @ Put current IOPAR on stack
    9600 BAUD                 @ Set 9600 BAUD Rate
    0 PARITY                  @ Set 0 Parity
    DO                        @ Main loop.  Get one key and process it.
      IFERR                   @ If ON/Cancel is pressed process below.
        DO                    @ Loop until a key is pressed (and released).
          KEY
        UNTIL END
      THEN                    @ KEY cannot get ON/Cancel, this code will catch it.
        IFERR                 @ KEY after cancel may or may not have put 0 on stack.
          DUP                 @ Setup IFERR again to error on DUP.
          0 == IF             @ IF DUP ok, check for 0 and DROP.
          THEN
            DROP
          END
        THEN
        END
        101                   @ Put 101 on stack if ON/Cancel
      END                     @ Bottom line, put KEY code on stack or 101 if ON/Cancel
     'k' STO                  @ Store k for key checks
      0 'v' STO               @ init v = 0
      k 25 == IF              @ Up pressed, build move string, set v = 1
      THEN
        27 CHR ",move,+0,-" + s + "." +
        1 'v' STO
      END
      k 35 == IF              @ Left pressed, build move string, set v = 1
      THEN
        27 CHR ",move,+0,+" + s + "." +
        1 'v' STO
      END
      k 34 == IF              @ Down pressed, build move string, set v = 1
      THEN
        27 CHR ",move,-" + s + ",+0." +
        1 'v' STO
      END
      k 36 == IF              @ Right pressed, build move string, set v = 1
      THEN
        27 CHR ",move,+" + s + ",+0." +
        1 'v' STO
      END
      k 105 == IF             @ Enter pressed, build click string, set v = 1
      THEN                    @   Enter is left double-click
        27 CHR ",dblclick,left." +
        1 'v' STO
      END
      k 81 == IF              @ Left-shift pressed, build click string, set v = 1
      THEN                    @   Left-shift is left mouse button
        27 CHR ",click,left." +
        1 'v' STO
      END
      k 91 == IF              @ Right-shift pressed, build click string, set v = 1
      THEN                    @   Right-shift is right mouse button
        27 CHR ",click,right." +
        1 'v' STO
      END
      k 95 == IF              @ + pressed, double s, i.e. pixel skip
      THEN
        s 2 * 's' STO
      END
      k 85 == IF              @ - pressed, half s, but always > 0
      THEN
        s 2 / CEIL 's' STO
      END
      v 1 == IF               @ if v = 1 send string
      THEN
        XMIT DROP             @ XMIT and ignore return code (bad)
      END
    k 101 == UNTIL            @ if k = 101 (ON/Cancel) exit loop and cleanup
    { HOME } EVAL             @ Stack should have 1:IOPARs, 2:PATH, cd to ( HOME )
    'IOPAR' STO               @ STO IOPARs as IOPAR
    EVAL                      @ PATH left on stack, EVAL to cd back.
    -78 CF                    @ Use USB.
    "DONE" DOERR              @ Done message in pretty box with beep.
  \>>
\>>

Fairly useless, use a real mouse!  For more information see [2].
 

Controlling EMU48

The previous program while useless does provide a framework for capturing any 50g keystroke (even ON), with the exception of multiple key sequences, e.g. ON+C.

The GREM program is a generic remote control program.  Instead of using countless tedious IFs, a list (SKBL) is used to define all 105 keys.  The SKBL included in the IOKB directory is perfectly suited for controlling EMU48 emulating a 50g.

%%HP: T(3)A(R)F(.);
\<< 0 \-> onc                 @ ON Button Counter = 0
  \<<
    -33 CF                    @ Wire Transfers
    -78 SF                    @ Use Serial Port (not USB)
    PATH                      @ Put current PATH on stack
    IOPAR                     @ Put current IOPAR on stack
    9600 BAUD                 @ Set 9600 BAUD Rate
    0 PARITY                  @ Set 0 Parity
    DO                        @ Main loop.  Get one key and process it.
      IFERR                   @ If ON/Cancel is pressed process below.
        DO                    @ Loop until a key is pressed (and released).
          KEY
        UNTIL END
      THEN                    @ KEY cannot get ON/Cancel, this code will catch it.
        IFERR                 @ KEY after cancel may or may not have put 0 on stack.
          DUP                 @ Setup IFERR again to error on DUP.
          0 == IF             @ IF DUP ok, check for 0 and DROP.
          THEN
            DROP
          END
        THEN
        END
        101.                  @ Put 101. on stack if ON/Cancel
        'onc' INCR DROP       @ If you got here, ON was pressed, increment 'onc'
      END                     @ Bottom line, put KEY code on stack or 101 if ON/Cancel
      DUP                     @ Copy KEY code for ON/Cancel check.
      SKBL SWAP GET           @ Get SerialKeys string from SKBL List and return to stack
      XMIT DROP               @ Transmit it, dump return code (we should probably check for error)
      101 \=/ IF              @ If no ON/Cancel, reset onc to 0
      THEN
        0 'onc' STO
      END
    onc 3 == UNTIL END        @ If onc == 3, i.e. three ON/Cancel in a row, then exit.
    { HOME } EVAL             @ Stack should have 1:IOPARs, 2:PATH, cd to ( HOME )
    'IOPAR' STO               @ STO IOPARs as IOPAR
    EVAL                      @ PATH left on stack, EVAL to cd back.
    -78 CF                    @ Use USB.
    "DONE" DOERR              @ Done message in pretty box.
  \>>
\>>

The only tricky part is knowing what to do with ON/Cancel.  The code above checks for 3 ONs in a row to exit.  To avoid any unpleasantness press the 3 ONs about 0.5 seconds apart.  This will avoid killing the program mid way.

The SKBL table is fairly strait forward.  The 50g keys are assigned with a range of 1 to 105.  Clearly there are less than 105 keys, so zeros are used as placeholders. 

%%HP: T(3)A(R)F(.);
{
  0 0 0 0 0 0 0 0 0 0
  "a" "b" "c" "d" "e" "f" 0 0 0 0
  "g" "h" "i" 0 "\027up." 0 0 0 0 0
  "j" "k" "l" "\027left." "\027down." "\027right." 0 0 0 0
  "m" "n" "o" "p" "\027bksp." 0 0 0 0 0
  "q" "r" "s" "t" "u" 0 0 0 0 0
  "v" "w" "x" "y" "z" 0 0 0 0 0
  "\027tab." "7" "8" "9" "\027kpstar." 0 0 0 0 0
  "\027lshift." "4" "5" "6" "-" 0 0 0 0 0
  "\027lctrl." "1" "2" "3" "\027kpplus." 0 0 0 0 0
  "\027esc." "0" "." " " "\027enter."
}

So what about multiple key sequences?  Well, it is easy with SerialKeys, but not with UserRPL.  A SysRPL or ASM program may be required to capture that.


Summary

That was easy!
 

Author

egan@sense.net
 

References

[1]  http://support.microsoft.com/kb/260517
[2]  http://support.microsoft.com/kb/260727/EN-US/


Appendix A

GENPOLY source:

%%HP: T(3)A(R)F(.);
\<< 0 \-> r n o                    @ get radius and number of size from stack, init o with 0
  \<<
    1 n FOR i
      2 \pi * n / i * \pi n / -    @ calculate angle and coordinates
      DUP
      SIN r * \->NUM
      SWAP
      COS r * \->NUM NEG
      2 \->LIST                    @ create point as 2 element list        
      1 i == IF
      THEN
        DUP 'o' STO                @ if first point, DUP and save as 'o' for later use
      END
    NEXT
    o n 1 + \->LIST                @ create list of points with first point (o) appended
  \>>
\>>