Scoreboards and Video Routing in Python
Controlling a Video Matrix
Although the basic operation of a matrix router is fairly simple to understand, it's always easier to have meaningful names and labels attached instead of remembering sets of numbers. Adding some shortcuts to common configurations makes things even easier, which is the purpose of the matrix control program (not shown, but available online [3]).
The Raspberry Pi communicates with the matrix over a USB serial connection set up in Python:
class matrixSerial: def __init__ ( self ): self.port = serial.Serial ( "/dev/ttyUSB0" , 9600 ) def send ( self , cmd ): self.port.write ( cmd )
The __init__
line creates self.port
, which is an instance of the Serial
object with the name of the serial device and the requested baud rate. The defaults are fine for everything else. The send
function takes in cmd
and transmits it with a call to self.port.write
, thus sending data to the matrix.
The Button Class
The main function of the program is to present easy options to the user on a touchscreen; a button
class sets up the actions to button presses. When the class is instantiated, it receives screen
, which is a reference to the Pygame screen object, and font
, which is a Pygame font object to be used to label the buttons. Each of these is stored in class properties for use by other methods.
When the create
function is called, it receives a number of variables to get started, including the x
and y
screen coordinates where the button should be drawn, the width
and height
of the button, and the label
text displayed on the button. self.rect
stores the Pygame rect
object, which has functions to check whether coordinates are within its bounds, along with several other useful utilities, and self.matrixCommands
and self.osCommands
are the actions taken when the button is pressed.
The addOsCommand
function takes its associated string argument and passes it to the operating system when the button is pressed:
def addOsCommand ( self , cmd ): self.osCommands.append ( cmd )
The addMatrixCommand
function is the same, except it transmits to the matrix.
The render
function is responsible for drawing the button on the screen. As you have seen before, it creates a surface and fills it – with green in this case. If its label
is not empty, the program renders the text, calculates how to center the text, and adds the text to the button blit
. The surface is then drawn to the screen with a reference saved to its Pygame rect
.
The Matrix Class
The matrix
class draws and manages the interface on the touchscreen. As usual, an __init__
function initializes the different components of the interface, but this example has a lot of them. The graphics system starts and creates a window to draw on. Although the window has a caption, it is generally not visible because the window is in fullscreen mode.
As usual, the fonts and serial communications are initialized, although pygame.font.SysFont
has an empty string as its first argument, thus asking Pygame for the default font:
buttonFont = pygame.font.SysFont ( "" , 32 ) self.matrixPort = matrixSerial()
The next line creates self.buttons
as a list
, which is where each of the buttons added to the interface are stored. Most of the rest of the init function creates each of the buttons and defines their functionality. They all follow the general format:
btn = button (self.screen, buttonFont) btn.create ( <x> , <y> , <width> , <height , "<label" ) btn.addMatrixCommand ( chr ( 0x05 ) + chr ( 0x55 ) +chr ( 0x19 ) + chr ( 0x00 ) ) btn.addMatrixCommand ( chr ( 0x05 ) + chr ( 0x55 ) +chr ( 0x19 ) + chr ( 0x11 ) ) self.buttons.append ( btn )
The first line creates a button
and passes in self.screen
and buttonFont
. The create
makes the button, and addMatrixCommand
adds commands for the video matrix. The command string comes from the matrix documentation; it tells you to send the first three hex values to start the command. The last byte contains the routing as 0
indexed addresses. The high-order digit defines the output or destination, and the low-order digit defines the input or source. Here, 0x00
says "connect the first output to the first input." The next line connects the second output to the second input. Table 1 shows the configuration for each button.
Table 1
Buttons and Configurations
Line Nos. | Button | Action |
---|---|---|
57-61 |
Separate Scoreboards |
Each display shows its own scoreboard computer. |
63-67 |
Single Scoreboard |
Both screens display the first scoreboard computer. |
69-73 |
Both Program |
Both screens display the video switcher program output. |
75-78 |
Court A Program |
Switch only the court A screen to the video switcher output. |
80-83 |
Court B Program |
Switch only the court B screen to the video switcher output. |
85-88 |
Court A Scoreboard A |
Switch only the court A screen to its scoreboard computer. |
90-93 |
Court B Scoreboard B |
Switch only the court B screen to its scoreboard computer. |
95-98 |
CR Gym 1 Action |
View Scoreboard (NUC) 1 on the control room monitor. |
100-103 |
CR Gym 2 Action |
View Scoreboard (NUC) 2 on the control room monitor. |
105-108 |
CR Program Action |
View the video switcher program output on the control room monitor. |
110-113 |
Record |
Start recording onto an SD card or flash drive. |
115-118 |
Stop |
Stop recording. |
One output of the matrix is connected to a monitor in the control room so that the sources can be easily monitored. The three CR buttons allow any of the matrix sources to be viewed on the control room monitor (Figure 7).
All of the buttons except the last two use addMatrixCommand
. The Record and Stop buttons use addOsCommand
instead and then use wget
to trick the digital recorder into thinking the buttons on its web GUI are being pressed to start and stop the recording onto an SD card or flash drive (Listing 2).
Listing 2
wget Trick
btn.addOsCommand ( r'wget --header="Accept:*/*" --header="Accept-Encoding: gzip, deflate" --header="Accept-Language: en-US,en;q=0.9" --header="Connection: keep-alive" --header="Cookie: serenity-session=72952074" --user-agent="Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0" "http://172.16.2.20/cgi-bin/system.cgi?command=recording&action=start"' )
The render
function iterates over self.buttons
, calling each render
method and creating all of the buttons onscreen, as well as all the Pygame rect
objects to see whether they've been clicked. Once everything has been drawn to the buffer, the function calls pygame.display.flip
to make it visible in the drawing window.
The loop
function makes everything interactive by watching for buttons presses, sending commands when they are, and keeping up with everything that's happening.
The looping
value is first set to True
, before the function enters the while
loop with looping
as its argument. If looping
is set at any point to False
, the loop exits.
The function then waits for Pygame events and checks to see if the event is a button click. To see if any of the buttons have been pressed, the function loops over self.buttons
again and gets the coordinates of the mouse click:
for btn in self.buttons: if btn.rect.collidepoint ( event.pos ) == True: for cmd in btn.matrixCommands: self.matrixPort.send ( cmd + chr ( 0x77 ) )
If True
, a button has been pressed. To make the button do its thing, the program loops over btn.matrixCommands
and sends each string to the matrix. To give the matrix time to process each command, the function waits half a second before moving on.
The lines that follow do the same thing for any operating system commands (btn.osCommands
) but pass them to os.system
instead. The call to matrix
on the last line of the program sets everything in motion.
Buy this article as PDF
(incl. VAT)