Pyro – Networking made simple

Converting Python to Pyro

In many cases, Python classes you've already written can be converted directly to Pyro objects just by adding a few decorators. Decorators are Python's way of extending the functionality of a class or function without explicitly modifying it. The following are some of the most common Pyro decorators on the class:

  • @Pyro4.behavior(instance_mode="single"): Tells Pyro to use a single instance of the class regardless of how many controllers connect to the daemon. When you're dealing with a finite resource (displays, bells, pixels), this mode helps prevent resource conflicts. You can also specify percall for the instance mode, which creates a new version of the daemon for every call, regardless of where it originates. Once the call is finished, the instance is discarded. If you don't specify an instance mode, Pyro uses session. A new instance of the daemon will be created for each unique controller that connects. Each controller can see changes it has made to its specific daemon, but multiple controllers will not share data.
  • @Pyro4.expose: Exposes an existing class to the Pyro network. When put above a class definition, every class method and property is then available to any controller that connects, which is handy if you can't modify the class itself; however, it can also introduce some security holes.

Common Pyro decorators for functions include:

  • @Pyro4.expose: Exposing specific methods inside classes is the preferred method of making class methods available to the Pyro network. Pyro handles the movement of all of the data across the network, and you call the method as if it were running locally.
  • @Pyro4.oneway: Tells Pyro to return immediately rather than wait for a response, which is preferred if you're going to do something that takes a long time. The downside is that you can't return a value to the controller. Any calls to this method return immediately to the controller, and the process is handled in its own thread on the daemon.
  • @property: Tells Pyro to treat the method as a read-only property. In this case, it is accessible through remoteObject.timeValue:
@property
def timeValue ( self ):
  return self.value
  • attr.setter: Tells Pyro to treat the method as a writable property:
@attr.setter
def editableTime ( self , newValue ):
  self.value = newValue

In this case, remoteObject.editableTime = 25.

Colorsquare Example

The Colorsquare example has two parts: a transmitter and a receiver. I'll look at receive.py (Listing 1) first. Pyro is imported just like any other library, and I import PyGame and time, as well (lines 3-5).

Listing 1

receive.py

 

Lines 8-16 initialize the Pyro class. The first Pyro decorator in line 8 tells Pyro to create a single instance of the class and use it for all requests. If you were to leave this out, each controller that connected to the class would spawn a separate instance of the class.

Line 11 defines the __init__ function, which works as expected, with one caveat – it is only called when something connects to the Pyro object. If nothing ever connects, then __init__ never runs. Lines 13 and 14 initialize PyGame, which draws the square onscreen, and line 16 stores the start time of the process, so run time can be calculated later.

The Pyro function appears in lines 22-28. @Pyro4.expose in line 22 tells Pyro that this function can be called by a connected controller. Class methods without this decorator won't be accessible with Pyro calls.

Lines 24 and 25 change the color of the square on the screen and then redraw the display so the new color is visible. As with most functions, return is found at the end (line 28). Pyro takes care of getting the return value back across the network to wherever the call initiated.

The other functions in lines 30-34, 36-40, and 42-46 all work exactly the same way. The only difference is a hard-coded color value. The getTime in lines 48-50 doesn't change the display; it simply returns how long the daemon has been running.

Now the code sets up Pyro and goes from just a class to a remote object (lines 52-61). To start, line 53 creates a daemon; then, the class is registered with the daemon (line 55) and returns its own URI. You can then save the URI in a database, write it to a temp file, or (as done here) use it to register with the name server. Line 57 finds the name server and line 59 registers it and lets it know how the service should be known. Now you can start listening and respond to calls as they arrive (line 61).

Setting up the controller is even easier (Listing 2). To begin, import Pyro (line 1) and find the name server (line 4), as in the receiver code. Next, search for the daemon to which you want to connect (line 6). Finally, create a proxy to the remote object (line 8).

Listing 2

transmit.py

 

These steps can be repeated as needed to connect to as many daemons as you like. Afterward, you can treat everything as if it is local.

The rest of the script asks for the user input (line 13) described earlier (Figure 3, bottom right) and then processes it. If it is a color known by name, the script calls that color's function (lines 16-18). If TIME has been requested, it is calculated and returned (line 19); otherwise, the program tries to parse the input as three comma-separated numbers (lines 22-26). The try/except exception handler (lines 22-25) will display a message if it can't find three numbers separated by commas.

Scoreboards

Each scoreboard runs identical code, with the exception of the Pyro name registered with the name server. To start the program on each Rasp Pi, I logged in via SSH from my desktop and started the Python script directly from the terminal. Each Rasp Pi had a separate tab, so any problems with connectivity or other errors could be checked from the console to see what's happening. My desktop was running the Python name server and controller, as well.

Each scoreboard had three major functions: updating the team name, updating the score, and displaying a timer. Once started, the timer mode was exited by pushing a stop button. Each button was wired to the Rasp Pi's GPIO via twisted pair wire. A simple header was connected to GPIO14 (physical pin 8, transmit) [2], which was selected because of the adjacent ground connection.

In the code walkthrough, I'll focus on the Pyro functions, although the majority of the code is actually for the graphics. As with any Python program, you have to start by importing libraries (Listing 3). In this case I import Pyro, pygame, time, and RPi.GPIO. Next, I set the mode of the GPIO pin numbering to the Broadcom SoC channel number, not the pin number on the board. Although I'm only using one GPIO pin, this is the pin numbering I'm used to.

Listing 3

display.py

 

The @Pyro4.behavior decorator (line 8) sets the instance_mode to single, so the class will be instantiated once, and all calls will use the same instance.

The __init__ method is standard, but remember that Pyro will not instantiate a class until it is actually called from a controller, which can make your first call seem a little sluggish as everything gets set up. Response times will improve afterward. As mentioned before, if nothing ever connects to the associated Python object, then the __init__ method will not be called.

Lines 11, 14, 19, 23, and 40 print status messages that I can monitor from the terminal from which I launch each daemon (Figure 4). Lines 12 and 13 initialize PyGame and set up a window in which to draw. The pygame.FULLSCREEN removes all window decoration and maximizes the window to take over the entire desktop.

Figure 4: Displays, Raspberry Pis, controllers, and daemons all talk to each other across various formats and protocols.

Next, I set up the fonts (lines 16-18); The init() starts the PyGame font module, and then pygame.font.Font loads a font from disk. The second argument specifies the font size in pixels. Each instance of a font object has the font itself and the size it should be rendered. If I want to use the same font in different sizes, I have to initialize it multiple times.

To set up the background image (lines 21-23), I load it from disk then add .convert() to the end to store it internally in the same format as my initialized display; self.screen.blit then draws it on the display.

The next group of lines that begin with self set default values for scores, team names, drawing color, and timers, with the timer status. I also set up some variables for blitting the background back onto itself to erase portions of the display.

The next lines (37-38) set up the GPIO – in this case, one input pin. The pull_up_down entry gives the input pin the electronic equivalent of a default value. In the absence of a signal, the pull up will make the pin read 1, or HIGH. The add_event_detect sets up a trigger so that when the GPIO pin falls (i.e., a button is pressed), a class method is called (self.bumpPoints). The timer functions will read the GPIO directly, but this allows calling the method in the background.

On the last day of camp, we introduced an Easter Egg into the scoreboard program. The buttons along the front of the stage allowed campers to add points to their team total. Once discovered, campers staged elaborate plans to keep pressing their buttons to run the scores up. Internally, these button presses call self.updateScore (line 43), awarding the team 10 points with each press.

Buy this article as PDF

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy Linux Magazine

SINGLE ISSUES
 
SUBSCRIPTIONS
 
TABLET & SMARTPHONE APPS
Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

  • DIY Scoreboard

    We look at a broadcast video system network that uses Python code to control a video router and check out another program that creates a scoreboard.

  • Nerf Target Game

    A cool Nerf gun game for a neighborhood party provides a lesson in Python coding with multiple processors.

  • Python generators simulate gambling

    Can 10 heads in a row really occur in a coin toss? Or, can the lucky numbers in the lottery be 1, 2, 3, 4, 5, 6? We investigate the law of large numbers.

  • Csound

    The powerful Csound software provides an impressive set of features for audio production and processing. We walk you through the entire system.

  • Panda3D

    Several free game engines are available for Linux users, but programming with them is often less than intuitive. Panda3D is an easy-to-use engine that is accessible enough for newcomers but still powerful enough for the pros at Disney Studios.

comments powered by Disqus
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters

Support Our Work

Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.

Learn More

News