HP Prime Programming

Program output of a Maurer rose and a famous hat demo from 1981 computer ads.

Code: spiro.ppl
(general python version)
Code: python program or app
(DM42 code for the same!)

1. Laptop or Calculator?

I work in a lab where laptops are pervasive. They pushed out calculators, which decades earlier did the same to slide rules. As calculators become more powerful, for some jobs the Prime could be more convenient than laptops and regain a spot on the workbench. We write python DSP code and while the Prime has the power to do some of that work, networking is needed to get signals onto it and results off it. Unfortunately, the Prime does not have Wifi, limiting greater use. But programmability keeps it attractive for small tasks and there are two options on the Prime: PPL (Prime Programming Language) and python.

PPL is well documented in the Prime user's manual and many web pages. Using information from the manual and some examples online, to learn PPL I wrote the small Spirograph-like program above that can be dropped into the HP Prime Programs folder visible from the HP Prime Connectivity Kit.

Python on the Prime is more challenging because in 2024 there is no formal HP python documentation. My first effort is the green hat above. You can use links below the picture to download either the program or the Prime App version. The App is dropped into Applications Library folder, and for some reason you must hit the 'Clear' soft button after starting the app.

2. Python Lessons Learned

Since official Prime documentation for python programming is yet to be written, I list a few things I've learned through googling and a good bit of trial and error. An important discovery is that the subroutine hpprime.eval('<PPL command>') is a way to get at PPL commands that don't have python equivalents. In particular, it allows easy access to menu-oriented subroutines so that you don't have to write your own. The paragraphs below all have the caveat that these are solutions I found. Please let me know if there are better ones.

2.1 Library Changes

2.1.1 Micro- and HP-python Libraries

Libraries are an important part of python's usefulness. The names of libraries and subroutines in each can be found on the Prime itself in the python app's CMDS menu. They are reproduced for firmware 2.1.14730 (2023 04 13) in this list.

2.1.2 numpy

Numpy (NUMeric PYthon) is a large library and unsurprisingly not part of micropython. Nevertheless, it is hard living without it. Your numpy vectors and matrices must be rewritten to use the Prime's linalg.matrix and will of course be constrained to what linalg routines exist.

2.2 Program Format

Every python program that is not based on the Python App needs a PPL wrapper:
  #PYTHON PPLwrapper
     print('Super complicated python functions go here!')
  #END

  EXPORT myFunc()
  BEGIN
    PYTHON(PPLwrapper);
  END;

The name you use rather than PPLwrapper is unimportant because the name that will show up in the program is myFunc. When you run the program, if there are multiple functions you will be prompted to choose which to execure.

2.3 Screen Tap Events

User taps on the screen are categorized as mouse events in the Prime. The user manual has details on the MOUSE() call. I simply reuse the following subroutines in each of my programs requiring mouse input:

  import hpprime as h

  def mouseClear(): # Clear out old mouse events like button bounces.
    while h.eval('mouse(1)')>=0:
      pass

  def mousePt(): # Wait forever for a screen touch.
    while True:
      h.eval('wait(0.1)') # Throttle i/o loop.
      f1,f2 = h.eval('mouse') # Touch info for fingers 1 and 2.
      if len(f1) > 0: # Got a finger touch!
        return f1,f2 # [x,y,xOrig,yOrig,type], [x,y,xOrig,yOrig,type]
  

mouseClear clears out any outstanding mouse events that we don't care about. If no events are waiting a -1 is returned and is why output of 0 or more is checked for. MOUSE(1) returns the x coordinate of a tap that would have been returned. mousePt is useful when your program must wait until there is a screen touch, for example when a soft menu choice is required. It returns data for both finger touches. The second finger data will be empty in the case of a single finger touch.

2.4 Pop Up Menu

Pop up menus can be created using the PPL subroutine CHOOSE():
  import hpprime as h
  c = h.eval('X:=0;choose(X,"Title","Choice 1","Choice 2","Choice 3")')

The first parameter to CHOOSE() must be defined before use and is any capital letter. The return value begins with 1 (not 0).

2.5 Soft Menu

The PPL routine DRAWMENU is an easy way to display custom menus. To act on a user press is a short sequence:
  • Wait on a mouse press using mousePt(), above.
  • Determine which button was pressed with softPick(), below.
  • Handle button function accordingly.

    The full code sequence for a program's main loop might be:

      import hpprime as h
      def main():
        while True:
          h.eval('drawmenu("Item0", "Item1", "Item2", "Item3", "Item4", "Item5")')
          m = mousePt()   # Get user screen tap coordinates.
          b = softPick(m) # -1 if not a soft menu tap.
          if b == 0:
            doItem0()
          elif b == 1:
            doItem1()
          elif b == 2:
            doItem2()
          elif b == 3:
            doItem3()
          elif b == 4:
            doItem4()
          elif b == 5:
            doItem5()
      

    Determining which soft button is pushed is easy keeping in mind that the six soft buttons are 53x20 pixels and the screen is 320x240 pixels.

      def softPick(pt): # pt is [x, y, xOrig, yOrig, type]
        return -1 if pt[1]<220 else pt[0]//53 # Soft button is 53x20 pixels.
    

    2.6 User Input

    As with other menu related functionality, a PPL subroutine is used to obtain user input:

    Line breaks are used for clarity. The first argument to input() is a variable list. These same variables are put in a list after the input() call completes to move values from the PPL call environment to python. Any valid variable name can be used in python. In this example, values of PPL D and F are assigned to d_m and f_Hz, which are used in subsequent python code. The second input() arg is pop up menu title and the third arg is the list of labels shown by each input box. The last arg of input() is more detailed help messages shown at the bottom of the Prime's screen when each input box is highlighted.

    Even relatively simple screen layouts quickly require somewhat complicated code. The screen shown here:

    is created with this python code snippet. Notice the use of variable res to determine if the user canceled or not:

    Details of INPUT() usage can be looked up in the manual's PPL command summaries. Note, however, the manual says that in the variables section the matrix [-1] can be used to indicate that the edit field accepts all data types. It results in an error for me, however, and I must use integers as given in the PPL TYPE() output list. I find myself most often using [0], indicating that only reals are accepted.

    2.7 User Output

    2.7.1 Present To Screen

    It is necessary sometimes to build up a string to pass to a PPL subroutine via eval(). For example, suppose I have built up a string in variable s that I would like to present on the screen:
      import hpprime as h
      cmd = 'msgbox("%s")' % s
      h.eval(cmd)
    

    2.7.2 Move To Prime Environment

    There are at least two ways to move program results to the calculator environment for later use.

    1. In a PPL Program, use syntax <program>.<variable> shown below.
    2. In an App derived from the Python app, use AVars() as shown in the next section.
    The syntax <program>.<variable> is used to get or set a value. For example, suppose program myProg contains a function myFunc. myFunc() runs python code as shown in this example:

    The steps shown in the picture above are:

    1. A PPL LOCAL variable is initialized, but how do we find it python?
    2. Simply specify program and variable name as shown.
    3. Suppose your program has done very hard work, here '1 + 1', and we want to save and share it.
    4. From PPL we see that the value has changed.

    Importantly, from the Prime Home and CAS environments the value is retrieved by typing myProg.myVar or using the same from another program. The hard work of this program can be utilized externally.

    Creating an app is only a very little bit more involved than writing a program, but is easiest to devote the following section to it.

    2.8 Creating an App

    Creating a python app is easy, but as mentioned earlier, you lose the tight integration with the Prime that is (only?) possible via HP PPL. In particular, PPL subroutines START(), RESET(), Info(), Symb(), Plot(), Num() and Setup counterparts of the last three can be implemented so that when those physical and soft buttons on the calculator are pressed, your subroutines are executed. I have not succeeded in doing the same in python. However, python has great appeal and step one is to create an empty app based on python:

    1. Press Apps button.
    2. Highlight Python icon.
    3. Press Save soft menu item.
    4. Rename the App.

    Next, add python files and an icon. The icon is what shows up on the Prime App screen and seems to be 38x38 pixels. To add files and icon start up the Connectivity Kit and turn on your calculator. Under Applications you'll see your new app and right click on its name:

    For each python and other needed files you must click 'Add file' and navigate to it. And same for your custom icon after clicking 'Add app icon'. After your code has been added to the app you simply choose it from the App icon list and then the soft menu 'Start' option. No doubt it will run bug free first try because you are an expert coder! :-) Here is a demo program you can add to an app:

    import hpprime as h
    
    def main():
      h.eval('AVars("MyVar"):=1234')
      h.eval('AVars("MyVar2"):=(3,4)')
      h.eval('AVars("MyVar3"):="Mike"')
      n=h.eval('AVars("MyVar")')
      c=h.eval('AVars("MyVar2")')
      s=h.eval('AVars("MyVar3")')
      h.eval('print') # Clear screen.
      print('n=1234 is now type %s' % str(type(n)))
      print('c=(3,4) is now type %s' % str(type(c)))
      print('s="Mike" is now type %s' % str(type(s)))
      print('Press + to end demo.')
      while True:
        h.eval('wait(0.2)') # Throttle i/o loop.
    
        # Retrieve mouse & keyboard events.
        b=h.eval('getkey') # Keyboard button press, -1 if none since last call.
        f1,f2=h.eval('mouse') # Finger 1, finger 2 touch data.
    
        if len(f1)>0: # Finger touch sensed.
          # Mouse data: [x,y,xOrig,yOrig,type].
          print('You touched (%d,%d)' % (f1[0],f1[1]), end='')
          if len(f2)>0: # Two finger touch.
            print(' and (%d,%d)' % (f2[0],f2[1]))
          else:
            print()
    
        if b==-1: # No button pressed.
          continue
        print('You pressed button %d' % b)
        if b==50: # + button exits demo.
          print('Slán!')
          break
    
    main()
    
    

    Finally, you must create the app's main program. I find it easiest to simply import my code and not otherwise put code here:

    The first roughly ten lines of main() show how you can share app results externally. This is necessary if you want to work with program results outside the program. I save an int, complex and string. The first three lines of output show what is retrieved

    and we see that all is well, though int becomes float. After the program runs, switch to CAS where you can retrieve and work with the results of your program:

    The infinite loop in the program above shows how to handle button pushes and screen touches. Screen touch data is returned for two fingers. You can try this demo code on your real Prime. Depending on your computer the two finger events might or might not register. The WAIT(0.2) ensures that I/O is only done 5 times a second. Next, both mouse and keyboard events are retrieved. If keyboard button number is -1 then no button was pushed since last check. If MOUSE returns [[],[]] then no screen touch happened since last check. The User Manual describes how GETKEY works and provides a useful diagram showing button numbering. For this demo code the "+" button stops the demo.

    2.9 HOME/PPL Bug

    AVars() is used share results from an app for use outside the program. However, there is a parsing bug in PPL when in the HOME environment. If there is a plus sign in the exponent, a valid construct, it results in an error. For example, 2e1 is parsed properly but 2e+1 incorrectly yields an error message. This is an issue because python generates numbers that way when the '%e' format is used. An excellent solution shared by Piotr Kowalewski uses cas.caseval():

    import cas
    def numToAvars(varName, val):
        cmd = 'AVars("%s"):=%s' % (varName, str(val))
        cas.caseval(cmd)
    

    To move a number out of the app to a variable named 'solution', your python program calls numToAvars:

    numToAvars('solution', myNumber)
    

    2.10 RF Calculations App

    I made an RF Calcs app that uses UI techniques learned, including placement of UI items, button debouncing and similar little things. It might be useful to see a working example.

    3. Feedback Welcome

    The preceding has been gleaned from trial and error, emailing, User Manual's section on PPL and a good bit of general web searching. I'm certain that there are better ways of doing some of the things I presented. If you can improve the above, please get in touch!

    4. Thank You!

    Thank you to Piotr Kowalewski and Eddie Shore for their help. I appreciate them sharing their expertise and helping me learn how to use my calculator. I'm the operator with my pocket calculator. :-)

    Many thanks,
    Mike Markowski, mike.ab3ap@gmail.com

    Web Analytics Made Easy - Statcounter