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)
Buy Linux Magazine
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.
News
-
Fedora 41 Released with New Features
If you're a Fedora fan or just looking for a Linux distribution to help you migrate from Windows, Fedora 41 might be just the ticket.
-
AlmaLinux OS Kitten 10 Gives Power Users a Sneak Preview
If you're looking to kick the tires of AlmaLinux's upstream version, the developers have a purrfect solution.
-
Gnome 47.1 Released with a Few Fixes
The latest release of the Gnome desktop is all about fixing a few nagging issues and not about bringing new features into the mix.
-
System76 Unveils an Ampere-Powered Thelio Desktop
If you're looking for a new desktop system for developing autonomous driving and software-defined vehicle solutions. System76 has you covered.
-
VirtualBox 7.1.4 Includes Initial Support for Linux kernel 6.12
The latest version of VirtualBox has arrived and it not only adds initial support for kernel 6.12 but another feature that will make using the virtual machine tool much easier.
-
New Slimbook EVO with Raw AMD Ryzen Power
If you're looking for serious power in a 14" ultrabook that is powered by Linux, Slimbook has just the thing for you.
-
The Gnome Foundation Struggling to Stay Afloat
The foundation behind the Gnome desktop environment is having to go through some serious belt-tightening due to continued financial problems.
-
Thousands of Linux Servers Infected with Stealth Malware Since 2021
Perfctl is capable of remaining undetected, which makes it dangerous and hard to mitigate.
-
Halcyon Creates Anti-Ransomware Protection for Linux
As more Linux systems are targeted by ransomware, Halcyon is stepping up its protection.
-
Valve and Arch Linux Announce Collaboration
Valve and Arch have come together for two projects that will have a serious impact on the Linux distribution.