How to write a Pyrame module in Python

This document describes how to implement a new module in Python and how to use it with cmdmod.

A module is a piece of software used to drive a piece of hardware or an abstract entity. We typically use a module to manage multiple devices of the same flavour. It is composed of two files: a description file in XML and an implementation file in Python. The cmdmod tool is used to run it.

In the following, we create from scratch a new module named “test”.

Creating the description file

The description file contains all the information needed by the module for running:

  • the name of the module
  • the used language
  • the code file complete path

It should look like this :

<config>
  <name>test</name>
  <file>/opt/pyrame/cmd_test.py</file>
  <language>python</listen_port>
</config>

Defining the TCP port of the module

The Pyrame module need to be adressed via a TCP socket described by its port. To manage properly all the ports and avoid conflicts, they are all stored in a file: /opt/pyrame/ports.txt file.

You just need to add the number of your new test module in that file this way

TEST_PORT=9212

By convention, we use the capitalized name of the module followed with _PORT as a name. Verify that the port you use is free on your machine (that is, it does not collide with another Pyrame module nor with some other network service).

Implementation of the Python module

Now that we have a description file, we have to implement the functions in a Python file.

The cmdmod extension

As stated in the cmdmod documentation, cmdmod is basically a virtual machine with a TCP/XML/Pyrame protocol decoder. Thus, the core of a module is a set of functions that respond to requests.

Note

Pyrame protocol does not accept to name the arguments of functions, so optional arguments must always be at the end of the list.

Hello world

This very basic function just returns 1 as return value (which means success) and “Hello World” as return text:

def helloworld_test():
  return 1,"Hello World"

Any time a return is encountered, the module exits from this function and send the result through the opened TCP socket. The result must have the form number,message. The number is an error code: 1 means “ok”, 0 means “error”. The message is the content of the result, it can be any string with no new line.

A more complicated example

Here is a little bit more complicated example showing parameters manipulation:

def helloworld_test(name):
  if name=="badname":
    return 0,"Helloworld function does not like badname"
  if name=="":
    return 1,"Hello World"
  else:
    return 1,"Hello %s"%(name)

Calling another module

To construct complicated systems, the modules have to rely on others. This is why a module can call another one with the execmd primitive.

Here is an example of a serial helloworld. It just opens a serial communication with a device having imprint “abcd:efgh” and sends Hello World on this serial link. Please see serial module documentation for details on the serial functions:

def serialhelloworld_test():

  #first we need to initialize the serial link
  #"usb_serial_converter" is an arbitrary name given to the link, we will use it as a handler in the sequel any time we need to use a serial function
  retcode,res=submod.execmd("init@serial","usb_serial_converter","abcd:efgh")
  if retcode==0:
    return 0,"cant init serial: %s"%(res)

  #here we write "Hello World\n" on the link
  retcode,res=submod.execmd("write@serial","usb_serial_converter","Hello World\n")
  if retcode==0:
    return 0,"cant write to serial: %s"%(res)

  #communication is over, we now need to deinitialize the serial link
  retcode,res=submod.execmd("deinit@serial","usb_serial_converter")
  if retcode==0:
        return 0,"cant deinit serial: %s"%(res)
  else:
        return 1,"Hello World successfully sent on serial"

You can see that in case of error, we pipeline the error reason to the next level

return 0,"error in something: %s"%(res)

This way, at any level we get the deepest error messages, allowing to debug easilly.

Testing the module

Now, our module is completed. We just have to install it and test it.

By convention, all the Pyrame modules are stored in the /opt/pyrame folder but you can place it wherever you want (accordingly with the path declared in the xml file).

Launching cmdmod

Launching cmdmod is very simple:

cmdmod /opt/pyrame/cmd_test.xml

It takes as its only parameter the description file with its path. The output of the module is the standard output. Optionally, you can add the “-d” flag to get debug messages.

You should see an output like:

test: 09:12:45 : module test language=python file=/opt/pyrame/cmd_test_py.py port=9212 up and running

Once you see this message, the module is ready to receive commands.

Sending commands with chkpyr

chkpyr2 is a tool that allows to send Pyrame commands through the command line. Its syntax is

chkpyr2.py host port function [parameters]

As we want to check our test module with the second version of the helloworld function, we should type

chkpyr2.py "localhost" "9212" "helloworld_test" "Fred"

Fred is the value for the name parameter. Double quotes are not necessary. Dont forget to escape your internal double quote with antislash. We get the result

retcode=1   res=Hello Fred

If you don’t want to use the numerical value of the port, type before:

. /opt/pyrame/ports.sh

You can then replace 9212 by $TEST_PORT:

chkpyr2.py localhost TEST_PORT helloworld_test Fred

You can pass a void argument

chkpyr2.py localhost TEST_PORT helloworld_test ""

and then we have the “Hello World” result

retcode=1   res=Hello World

Finally if we use the “badname” value

chkpyr2.py localhost TEST_PORT helloworld_test badname

We get an error

retcode=0   res=Helloworld function does not like badname

Other languages

Cmdmod has support for other languages than Python. You can use Lua, C, C++, shell or R to code your module. Please, have a look at the test programs to get syntax details:

/opt/pyrame/cmd_test_lua.lua
/opt/pyrame/cmd_test_c.c
/opt/pyrame/cmd_test_cpp.cpp
/opt/pyrame/cmd_test_sh.sh
/opt/pyrame/cmd_test_r.r
/opt/pyrame/cmd_test_py.py