Here’s the overall architecture of the solution:
The matrixSvr.py program is intended to run permanently on the RPi (a cron job will check to see if it’s running and start it if not). That way, it can keep track of the status of the printer and process any incoming requests to print files.
And the phrase ‘any incoming requests’ is a clue to why I’ve tackled this project in the way I have. While I’ve implemented the web-based front-end, I can also foresee situations where, say, other Python or C++ programs might communicate with matrixSvr.py to get it to print text or documents for them. So matrixSvr.py is intended as a printing service with multiple applications.
I’ve had situations before where I’ve needed one running process running on a Raspberry Pi or BeagleBone to talk to another. And there are many ways of doing this, apparently, including solutions such as shared memory and even MQTT. But the one I opted for is very simple – just writing messages to a file.
I’ve developed a small Python library for this, which I’m not going to walk you through because it would be boring. But in the case of matrixSvr.py it works like this.
The server program (matrixSvr.py) regularly looks at a message file – in this case, matrixSvr.msg. If there’s text in there it retrieves it, treating each line of text as a separate message. It stores these messages in an internal queue while also deleting them from the file. Each time through its main loop it also plucks the top message from the queue and acts on it in the appropriate way.
If other programs want to send a message to matrixSvr.py, they just write a line of text to the end of the file. The traffic is all one way – ie, a message file is unique to a given program which only reads it while other programs only write to it.
To prevent programs getting in each other’s way – eg, several programs trying to write at the same time, or the server program trying to read while something else is trying to write – I use a simple lock file (for which I have another Python class). This has the same name as the message file but with ‘.lock’ appended. In order for any program to read or write to the message file, it first looks for the existence of the lock file. If there is no such file, the program creates it, does its business, then deletes the lock file. If the lock file exists, the program must wait until it’s gone (and it’s up to individual implementations whether to use a timeout).
With my Python library I’ve also gone further and defined a format for messages, which is:
The sender_id is usually the name of the program sending the message. So, for example, when my front-end Ajax server wants to tell matrixSvr.py to print a file, it writes something like the following into the message file:
The message here is ‘print>>testfile.txt’. Several messages may have command/parameter components and, in this case, I’m using ‘>>’ as a delimiter.
It all works surprisingly well for the low volume of messages I’m dealing with here.
There’s another place I use a text file, too. The matrixSvr.py server needs to be running all the time. But what if there’s a crash? Other programs might want to know if it’s running okay.
Here’s what I do. I’ve written a class that I include in programs like matrixSvr.py. When the program runs, it checks for the existence of a named file – in this case matrixSvr.pid. Hopefully, it shouldn’t find it. If that’s the case, it creates the file and stores inside it’s own process ID (PID) number.
If it does find the file on first running, this could mean one of two things – either an instance of the program is already running, or it was running, has crashed and has left behind an orphaned file. So the program opens the PID file, looks at the number stored there and looks to see if there’s a current process with that value. If so, it can decide what to do – normally it will try to kill that process and, if that’s successful, write a new PID file and carry on. If it’s unsuccessful, it exits. If it doesn’t find a running process, it overwrites the PID file with its own process ID.
Meanwhile, other programs can use similar processes to work out if matrixSvr.py is currently running.
None of this is bulletproof, but so far it’s proven to be pretty robust in practice.
Change of plan
One thing that came out of working on matrixSvr.py is that there’s one function in my front-end solution that I won’t be able to implement. From the web page I can check if the server is running, but I also wanted to be able to start it if it wasn’t.
Alas, the web server runs as user ‘intranet’ but matrixSvr.py needs to be run as root. This is because it needs control of the Raspberry Pi’s GPIO pins. Even if I try adding the sticky bit as root, I can’t get the program to work when run by intranet – it just doesn’t have the permissions. Maybe I can find a way around that, but I can live without this feature.
I have added a button to initialise the printer. And now, once I’ve selected a file, in addition to the print button there’s another that allows me to delete the file, after confirming via a dialogue box.