I'd like to be able to get a notification from a network printer when it physically finishes printing a page (and/or the entire job). This is going to be used in an app I'm writing for print management via web, and since users get charged per-page, and the charge shouldn't go down before the page actually completes.
I'm not sure whether this requires writing a driver, some kind of plug-in or whether a client app will do. I am flexible with my platform, since my client isn't written yet, so I'd like to hear about any suitable solution in either Windows or Linux, in any programming language/level.
I'm aware that there is a difference between the spooler and the printer. I'm trying to examine at which level the printer might notify the machine, via IPP, when the page or job physically complete.
I'm currently looking into Java, using either jspi or cups4j package to obtain a notification when the IPP property job-impressions-completed
changes, or alternatively, poll for it. I'm using a CUPS IPP interface to a local printer. Running a simple tester (HelloPrint.java
attached below; or the CupsTest.java
included in cups4j), I did not receive any job-impressions-completed
attribute changes nor did it list the attribute for the job when I was polling.
So here are the questions:
job-impressions-completed
attribute isn't being updated, specifically since it acts as a spooler for the real printer. Assuming that the real printer will notify about or list this attribute, Would this be printer-specific or must any IPP-supporting printer have this attribute available and updated?System info: Ubuntu 11.10, CUPS 1.5.0, printer is Brother HL-2240D (PPD available here)
Note: HL-2240D is not the printer I will be using for the eventual project (specifically, it doesn't support IPP); I'm intending to use either an HP HL4250DN or Samsung 3741ND or similar.
Here's a sample app using the javax.print
packages and jspi:
HelloPrint.java
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Date;
import javax.print.*;
import javax.print.attribute.*;
import javax.print.attribute.standard.*;
import javax.print.event.*;
import de.lohndirekt.print.IppPrintService;
public class HelloPrint {
/**
* @param args
*/
public static void main(String[] args) {
// create request attributes
PrintRequestAttributeSet requestAttributeSet = new HashPrintRequestAttributeSet();
requestAttributeSet.add(MediaSizeName.ISO_A4);
requestAttributeSet.add(new Copies(1));
requestAttributeSet.add(Sides.DUPLEX);
// find an appropriate service
// using jspi (http://code.google.com/p/jspi/)
URI printerURI;
try {
printerURI = new URI("ipp://localhost:631/printers/HL2240D-local");
} catch (URISyntaxException e2) {
e2.printStackTrace();
return;
}
IppPrintService service = new IppPrintService(printerURI);
// by enumerating
// PrintService[] services = PrintServiceLookup.lookupPrintServices(
// DocFlavor.INPUT_STREAM.PDF, requestAttributeSet);
// for (PrintService service1 : services) {
// System.out.println(service1);
// }
// PrintService service = services[0];
// add listeners to service
service.addPrintServiceAttributeListener(new PrintServiceAttributeListener() {
@Override
public void attributeUpdate(PrintServiceAttributeEvent event) {
PrintServiceAttributeSet serviceAttributeSet = event
.getAttributes();
StringBuilder s = new StringBuilder();
s.append("=== PrintServiceAttributeEvent: (" + serviceAttributeSet.size() + " attributes)\n");
for (Attribute attribute : serviceAttributeSet.toArray()) {
PrintServiceAttribute printServiceAttribute = (PrintServiceAttribute) attribute;
s.append(printServiceAttribute.getCategory().getName()
+ "/" + printServiceAttribute.getName() + " = "
+ printServiceAttribute.toString() + "\n");
}
System.out.println(s.toString());
}
});
// add file (blank.pdf is a blank page exported as PDF from LibreOffice
// Writer)
FileInputStream inputStream;
try {
inputStream = new FileInputStream("blank.pdf");
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return;
}
// create a new doc and job
DocAttributeSet docAttributeSet = new HashDocAttributeSet();
docAttributeSet.add(MediaSizeName.ISO_A4);
docAttributeSet.add(Sides.DUPLEX);
Doc doc = new SimpleDoc(inputStream, DocFlavor.INPUT_STREAM.PDF,
docAttributeSet);
DocPrintJob job = service.createPrintJob();
// listen to print job attribute change events
// attribute set is null, means this means to listen on all dynamic
// attributes that the job supports.
job.addPrintJobAttributeListener(new PrintJobAttributeListener() {
@Override
public void attributeUpdate(PrintJobAttributeEvent event) {
PrintJobAttributeSet jobAttributeSet = event.getAttributes();
StringBuilder s = new StringBuilder();
s.append("=== PrintJobAttributeEvent: (" + jobAttributeSet.size() + " attributes)\n");
for (Attribute attribute : jobAttributeSet.toArray()) {
PrintJobAttribute jobAttribute = (PrintJobAttribute) attribute;
s.append(jobAttribute.getCategory().getName() + "/"
+ jobAttribute.getName() + " = "
+ jobAttribute.toString() + "\n");
}
System.out.println(s.toString());
}
}, null);
// listen to print job events
job.addPrintJobListener(new PrintJobListener() {
@Override
public void printJobRequiresAttention(PrintJobEvent pje) {
System.out.println("=== PrintJobEvent: printJobRequiresAttention");
}
@Override
public void printJobNoMoreEvents(PrintJobEvent pje) {
// TODO Auto-generated method stub
System.out.println("=== PrintJobEvent: printJobNoMoreEvents");
System.out.println(pje.getPrintEventType());
System.out.println(pje.toString());
}
@Override
public void printJobFailed(PrintJobEvent pje) {
// TODO Auto-generated method stub
System.out.println("=== PrintJobEvent: printJobFailed");
System.out.println(pje.getPrintEventType());
System.out.println(pje.toString());
}
@Override
public void printJobCompleted(PrintJobEvent pje) {
// TODO Auto-generated method stub
System.out.println("=== PrintJobEvent: printJobCompleted");
System.out.println(pje.getPrintEventType());
System.out.println(pje.toString());
}
@Override
public void printJobCanceled(PrintJobEvent pje) {
// TODO Auto-generated method stub
System.out.println("=== PrintJobEvent: printJobCanceled");
System.out.println(pje.getPrintEventType());
System.out.println(pje.toString());
}
@Override
public void printDataTransferCompleted(PrintJobEvent pje) {
System.out.println("=== PrintJobEvent: printDataTransferCompleted");
System.out.println(pje.getPrintEventType());
System.out.println(pje.toString());
}
});
// print
try {
job.print(doc, requestAttributeSet);
} catch (PrintException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
return;
}
// try polling
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
return;
}
System.out.println("=== Polling: I'm alive and it's " + new Date());
System.out.println("Job attributes");
for (Attribute attribute : job.getAttributes().toArray()) {
System.out.println((attribute.getCategory().getName() + "/"
+ attribute.getName() + " = " + attribute.toString()));
}
System.out.println("Service attributes");
for (Attribute attribute : service.getAttributes().toArray()) {
System.out.println((attribute.getCategory().getName() + "/"
+ attribute.getName() + " = " + attribute.toString()));
}
}
}
}
Select a printer, and, at the top of the window, click Print server properties. From the Advanced tab, check or uncheck Show informational notifications for local printers and Show informational notifications for network printers.
Open the Devices and Printers folder, right-click the HP UPD, and then select Properties. Click the Device Settings tab. In the Installable Options section, set Printer Status Notification to either Enable or Disable. Click OK.
In the end it all depends on the printers firmware. IPP specifies the attribute job-impressions-completed as optional. That means if the printer can't tell which page has been printed, you won't be able to read it - no matter whether your programming is correct or not.
Manufacturers usually claim to support IPP but don't document very well the optional parts they might have implemented (or not).
Before doing any programming I suggest to read all available job-attributes using the ipptool
available from CUPS:
#!/usr/bin/env ipptool -tv -d job=482 ipp://192.168.2.113/ipp
{
OPERATION Get-Job-Attributes
GROUP operation-attributes-tag
ATTR charset attributes-charset utf-8
ATTR language attributes-natural-language en
ATTR uri printer-uri $uri
ATTR integer job-id $job
}
job-state
is a mandatory attribute and should reach a final state after some time: completed
, aborted
or canceled
. This might be good enough, if you can get the number of job-pages somewhere else.
Implementation hint: IppJob provides method waitForTermination()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With