-
Notifications
You must be signed in to change notification settings - Fork 2
How Printing Works
We made use of Protocol Buffers and Remote Procedure Calls (RPCs) to send print requests from our website to the two printers in SCE. The two technologies allowed us to connect our website written in JavaScript to a printer server written in Python.
Since Google Cloud printing is going out of service, we are developing a new method of printing. Using the LPD protocol, we no longer need to rely on other services to host our printing page.
We use the client-server model where the client is written in node
and the server is written in python. To print, call the following
function in printing_client.py
function sendPrintRequest(raw, copies, sides, pageRanges, destination)To modify the printing options, change the following
line in printing_client.js
const printOptions = {
'sides': 'one-sided',
'page-ranges': 'NA'
};The printer RPC is called from print_client.js. An example of the function
is invoked below:
printOptions = {
'sides': sides,
'page-ranges': pageRanges
};
let client = new services.PrinterClient('localhost:50051',
grpc.credentials.createInsecure());
let request = new messages.PrintRequest();
request.setCopies(copies);
request.setDestination(destination);
request.setEncodedFile(raw);
for (let key in printOptions) {
request.getOptionsMap().set(key, printOptions[key]);
}
return new Promise(function (resolve, reject) {
client.printPage(request, function (err, response) {
if (err || response.getMessage() == 'error') {
reject({ message: 'Failed to print', error: true })
}
resolve({
message: response && response.getMessage(),
error: false
})
})
})We first define the client to be of the Printer service type. As mentioned
above, the Printer service contains the RPC PrintPage, which is
implemented in the printing_server.py file.
We then initialize a PrintRequest proto, setting each proto field to what we
send appropriate values.
We then invoke the PrintPage RPC from the .js file, sending a
PrintRequest protobuf to the RPC. In this process, we return a promise so
we know if the function call succeeded.
Our server side PrintServicer is written in Python
in the file printing_server.py.
class PrintServicer(print_pb2_grpc.PrinterServicer):
def DeterminePrinterForJob(self, copies):
pass
def SendRequestToPrinter(self, encoded_file, copies=1, options={}):
# Contains LPD commands
pass
def PrintPage(self, request, context):
response = print_pb2.PrintResponse()
response.message = self.SendRequestToPrinter(
encoded_file=request.encoded_file,
copies=request.copies, options=request.options)
return responseThere is a PrintServicer object that has the
server functions derived from the automatically generated
print_pb2_grpc files from the proto.
We generate a response, which represents the status of
the printer. SendRequestToPrinter is then called, which executes
the printing procedure and returns a status.
The resulting status is sent back to the client.
class PrintServicer(print_pb2_grpc.PrinterServicer):
pages = 0
def DeterminePrinterForJob(self, copies):
if (self.pages > 0):
self.pages += copies
return "HP-LaserJet-p2015dn-right"
else:
self.pages -= copies
return "HP-LaserJet-p2015dn-left"There is a DeterminePRinterForJob function that determines which
printer in the SCE to send the job to. This allows us to balance
the printing so we do not have to constantly refill a printer.
def serve():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
print_pb2_grpc.add_PrinterServicer_to_server(PrintServicer(), server)
print('Starting server. Listening on port 50051.')
server.add_insecure_port('[::]:50051')
server.start()
server.wait_for_termination()First, the server is defined to be a
RPC server type with a maximum of 10 threads.
Then, PrintServicer and its functions are added to the server.
The server is configured to listen on port 50051.
Finally, the server is started until it is terminated.
The two printers currently found in SCE are both HP LaserJet P2015DN.
Inside of print.proto, we can see the PrintRequest protobuf defined below:
message PrintRequest{
uint32 copies = 1;
string destination = 2;
map<string, string> options = 3;
bytes encoded_file = 4;
}The first field of PrintRequest is copies, which specifies the number of
times we will print the file. The destination field holds the name of the
printer we will print to. Because we have two printers, the value of
destination is one of two constants. Finally, the options field holds a
map of key value pairs, both of type string. This is to specify any
additional options that can be sent via the LPD printing protocol. To read
more about the possible options, see this
manual
for the lp command. Finally, bytes encoded_file is the raw pdf bytes, which will later be decoded later.
message PrintResponse{
string message = 1;
}The only field of PrintRequest is message which either contains a
confirmation or an error if the print job finished.
RPC stands for remote procedure call. It is similar to a function call to a function that is implemented elsewhere. In our code, we used an RPC service to call a python function from a client written in nodejs. This is crucial for our printing page since the execution of the printing is written in python while the data is sent through our front end in nodejs. This website gives a well documented explantion of RPC's and how to start using them.
LPD printing is the main protocol used in the printers in the SCE.
Printing is controlled through a bash command lp, as seen in
this function in printing_server.py.
def SendRequestToPrinter(self, encoded_file, copies=1, options={}):
SendRequestToPrinter takes in raw pdf data and creates a temporary file out of it.
Then, it uses its other parameters to determine where, how many, and
how the file should be printed. Finally, the temporary file is
deleted.
A possible resulting command would be
lp -n 1 -o sides=one-sided -d HP-LaserJet-p2015dn sample.pdf
Breaking down this command:
-
-n: This represents the number of copies the printer will print.
Ex:
-n 2will print out 2 copies of a document -
-o: This represents options.
In our code, we have a dictionary of options to choose from, like
sidesandpage-ranges. - -d: This represents the destination printer.
- sample.pdf: This is the PDF file that will be sent to the printer.
To understand more about LPD, type in man lp in terminal or visit
the LP Manual Page.
Initially, we tried using the IPP (Internet Printing Protocol) protocol to print. However, the printer was too old so it was unable to use the protocol. When we tried printing a file using IPP, the unformatted raw pdf of the file was printed.
We soon realized that the printers were manufactured in 2006, and still used LPD (Line Printer Daemon Protocol).
We were unable to connect to the printers through the school's wifi. We later saw that the printers had an ethernet cable connected to them. We found the IP address by printing out the network configuration page and established a successful connection through ethernet.
We attempted to look at the old working printing page for clues. However, the old code consisted of tens, possibly hundreds, of thousands of lines that were undocumented. It was difficult to follow the code since it was all over the place and repetitive. The repository of the existing system can be found here