[comp.sys.ibm.pc.misc] Summary Printer check - BIOS/DOS - various languages

pcampb@shiva.trl.oz (Peter Campbell) (08/22/90)

This is a repost - our news server has been dead lately, I didn't even
see this article when I sent it.  Apologies to anyone it made it to.


Thanks to all who sent answers re checking if printers are connected to
a machine.  All answers unfortunately fell into the category of BIOS
functions, but I finally managed to get a hold of the MS-DOS
encyclopedia and managed to work out the way to do it in DOS.
Thanks especially to Clarence Dold & Lance Bresee.

Here is the BIOS version (in C) from Clarence Dold, et.al:
(See below for comments)

> On my AT-Clone, with an Epson RX-80, I find that status truly boils down
> to three bits: NOT_BUSY, PAPER_OUT, and IOERR
> We could say that there are six states for the printer:
> 1- No Printer I/O Card			0x00
> 2- Printer not connected			0x30
> 3- Printer Off				0x98
> 4- Printer Offline				0x18
> 5- Printer Out of Paper			0xb0
> Offline and out of paper			0x38
> 6- Online and ready.				0x90
> In each case, BIOS INT 0x17, ah=2 returns	^
> 
> I didn't bother with checking if BUSY was really BUSY with a print task,
> since we were talking about a 'pre-print' on-line check.
> I also don't know how shared printer usage affects the return.  I suspect
> that it would give 'off line' in either case.
> 
> /* for QuickC, CADold cdold@tsdold.Convergent.COM */
> 
> #include <stdio.h>
> #include <dos.h>
> union   REGS    regs;
> 
> #define PRT_BIOS 0x17
> #define PRT_STAT 0x02
> #define PRT_PORT 0x00
> #define NOTBUSY 0x80
> #define PAPER_OUT 0x20
> #define IOERR 0x08
> 
> main()
> {
> 
> int prt_stat, retstat=1;
> regs.h.ah = PRT_STAT;
> regs.x.dx = PRT_PORT;
> int86(PRT_BIOS, &regs, &regs);
> prt_stat=regs.h.ah;
> printf("Port Status: %x\n", prt_stat );
> 
> switch(prt_stat & ( NOTBUSY | PAPER_OUT | IOERR) ) {
> /* masking off unintelligent bits */
>	case 0x00:
> 		printf("No Printer Port\n");
> 		break;
> 	case 0x08:
> 		printf("Printer is Offline\n");
> 		break;
> 	case 0x20:
> 		printf("Printer is not connected\n");
> 		break;
> 	case 0x28:
> 	case 0xa0:
> 		printf("Printer is Out of Paper\n");
> 		break;
> 	case 0x80:
> 		printf("Printer is Online and Ready\n");
> 		retstat=0;
> 		break;
> 	case 0x88:
> 		printf("Printer is Turned Off\n");
> 		break;
> }
> void exit(retstat);
> } /* end of main */
> 

This function makes use of the standard BIOS call with Interrupt 17H (H
hex, given in PRT_BIOS) and function 02H (PRT_STAT) - get printer
status.

Seeing as this is a fairly standard BIOS function for (most) IBMs, and
IBMs being what they are, it won't work.  (I have a great respect for
IBMs - a great respect for their proof of Murphy's Laws.)

To get most printers to respond correctly you have to actually send a
character to the printer.  I have found that if you use function 01H
(initialise printer) it just initialises a device, sending nothing to
the printer really, and may stuff up anything you've done to use a
non-standard printer.  The printer can be turned off and the return
status will be "o.k.".  Function 02H seems to use the PREVIOUS printer
call to work out if the printer is working.  Hence, to get the above
working you should make the following changes:

#define PRT_STAT 0x00	/* Send byte to printer */
#define CAR_RET  0x0D	/* Carriage Return */
...
regs.h.al = CAR_RET;

This sends a <carriage return> to the printer, so the printer has to
actually do something and you can be assured of getting some response in
general.

The above sort of thing can be done in Turbo C using the predefined
function biosprint - see the reference guide if you've got it.

However (you knew this was coming, didn't you :-), if you have a
non-standard BIOS the above may simply not work (but you'd have to be
pretty unlucky).  The main trouble is that only Epson and the standard
(if there is one) IBM printer are guaranteed to produce all of the above
error messages (there may be a few others, don't sue me, but I don't
know of them).  Hence, for example, you may get an 'IO Error' from some
printers when it's out of paper.  Some printers will only give you the
'IO Error' message no matter what the error is, so it's best to take
the error messages with a grain of salt.  Furthermore, this sort of
thing might not work with people using OS/2, as it is BIOS specific.

Hence, try the following changes.  This uses the DOS interrupts to send
a character to the printer.  Unfortunately the return error codes aren't
nearly as specific for those of the BIOS call, even for standard
Epson/IBM printers.  For example, most errors like Timeouts and
occasionally Printer Out of Paper will give a write fault on most
machines.

This function uses the predefined handle 4, which is set to the default
printer (e.g. prn, usually lpt1).  Unfortunately, to get at the return error
message you have to get at the critical error handler, i.e. what is invoked
if an interrupt 24H occurs.  This is what puts up messages like "Drive not
ready" when your printer is turned on, for instance.  The reason it does this
is because DOS uses the EXACT same type of DEVICEs for printers, files, drives,
etc., and can not distinguish between them.

For Borland's Turbo C at least, the above routine can be adjusted as follows:


#include <stdio.h>
#include <dos.h>
union	 REGS	 regs;
struct	SREGS	sregs;
#define PRT_DOS 0x21
#define WRITE_DEVICE 0x40
#define HANDLE 0x04		/* Predefined Handle 4 - Printer */
#define CHAR_NUM 0x01		/* 1 Character to be sent to printer */
#define IGNORE 0
#define RETRY 1
#define ABORT 2

int handler(int errval, int ax, int bp, int si)
/* This is where the actual error reporting is done */
{
switch (errval)	{
	case 0x00:
		printf("Disk write protected\n");
		break;
	case 0x02:
		printf("Drive, Network or Printer not ready\n");
		break;
	case 0x09:
		printf("Printer out of paper\n");
		break;
	case 0x0a:
		printf("Write fault or Printer Timeout\n");
		break;
	case 0x0b:
		printf("Miscellaneous Read Fault\n");
		break;
	case 0x0c:
		printf("General Failure\n");
		break;
	default:
		printf("Unexpected Error\n");	/* like CRC error, etc. */
		break;
}
return(ABORT);	/* Simple option - could give choice depending upon error */
}

main()
{
harderr(handler);	/* Tell program what to use for error handler */
unsigned char cr='\xD';	/* Carriage Return */
#define ptr (char far*)&cr;/* cast cr to get segment and offset */

int anerror, retstart;
regs.h.ah = WRITE_DEVICE;
regs.h.bl = HANDLE;
regs.h.cl = CHAR_NUM;
regs.x.dx = FP_OFF(ptr);	/* address offset of cr */
sregs.ds =  FP_SEG(ptr);	/* address segment of cr */

/* int86x(PRT_DOS, &regs, &regs, &sregs); - Can use this, but might as well use: */

int ret;
ret = intdosx(&regs, &regs, &sregs);	/* DOS interrupt 21H */
/* If carry flag is set, then error occurred */
anerror=(regs.x.cflag ? ret : 0)

switch (anerror) {
	case 0x00:
		printf("No Error reported");
		retstart=0;
		break;
	default:
		printf("An Error has been reported\n");
		retstart=1;
		break;
}
void exit(retstat);
} /* end of main */

This should work for all memory models, but my C is a bit rusty, plus I 
transferred it from prolog.

I've been told that Timo Salmi has a copy of a Pascal or Turbo Pascal
function which does the same thing as above sitting on chyde@uwassa.fi
in Finland (IP # 128.214.12.3) that you can get at using anonymous ftp -
I think it was in /pc/pd2 somewhere, but don't quote me.  No, I can't
remember the name of the file.

The Turbo/PDC Prolog version is given below, so stop here if C is all you
want.

The Prolog version below also has the added extra of having the printer
timeout length changed.  The addresses changed are BIOS, so I didn't give
them above.  You also won't be able to find any word of them in any
DOS manual - these come from the Norton Guides which a friend of mine has.
Normal timeout values are set at $20, which, depending upon the machine
used, can be anywhere from 20-100 seconds.

------------------------------------------------------------------------
/* The following is all Turbo/PDC Prolog code until otherwise stated. */
/* Feel free to cannabalise, flog, or otherwise use any of the code
   given below, unless Borland or PDC say otherwise. */


%% Written by PKC 6/8/90 - Prolog upsets registers, so can't test DOS
% interrupts properly without redefining critical error handler

global database - printer_check
    determ printer_error	%% PKC 8/8/90 asserted if critical error 

global predicates
	criticalerror(integer,integer,integer,integer) - (i,i,i,o) language c
%	%% PKC 6/8/90 Critical Error Handler, as defined User Guide p.441
%	criticalerror(integer,		% (i) ErrNo
%		      integer,		% (i) ErrType
%		      integer,		% (i) DiskNo
%		      integer)		% (o) Action 
%		- language c

predicates

	% Give String for error number
    error_type(integer,		% (i) Error Number
	       string)		% (o) Error Type

	% Assert database predicate to indicate error has occurred.
    assert_action(integer)	% (i) Action - assert d/b flag if 0 = abort

	% Report type of error & ask user to cancel, retry or (maybe) ignore
    report_error(integer,	% (i) General Error Number
		 integer)	% (o) Action (0 abort, 1 retry, 2 ignore)

clauses
	% Give String for error number
    error_type(0,"Disk Write Protected"):-!.
    error_type(2,"Drive, Network or Printer not ready"):-!.
    error_type(9,"Printer out of Paper"):-!.
    error_type(10,"Write Fault or Printer Timeout"):-!.
    error_type(11,"Miscellaneous Read Fault"):-!.
    error_type(12,"General Failure"):-!.
    error_type(_,"Unexpected Error"):-!.

	% Assert database predicate to indicate error has occurred.
	% Only used for printer testing.
    assert_action(0):-assertz(printer_error),!.
    assert_action(_):-!.

	% Report type of error & ask user to cancel, retry or (maybe) ignore
    report_error(ErrNo,Action):-
	not(list_member(ErrNo,[0,2,9,10,11,12])),!,
	menu(10,10,30,15,["Abort operation",
			  "Retry operation",
			  "Ignore (not recommended)"],
    		 	  "An unexpected error has occurred",
    		 	  "ErrorARI",1,Choice),
   	Action=Choice-1.
    report_error(ErrNo,Action):-
	error_type(ErrNo,ErrString),!,
	menu(10,10,30,15,["Abort operation",
			  "Retry operation"],
    		 	  ErrString,"ErrorAR",1,Choice),
   	Action=Choice-1,
   	assert_action(Action).
    report_error(_,0):-!.		% Catchall - abort

	% Redefinition of the critical error handler
    criticalerror(ErrNo,_,_,Action):-
	retractall(printer_error),
	error_type(ErrNo,ErrString),
	not(ErrString="Unexpected error."),
	report_error(ErrNo,Action),!.
    criticalerror(_,_,_,0):-!,
	retractall(printer_error),
	assertz(printer_error).

predicates
	% determine which media are attached to program
   determine_media(integer)	% (o) Flag, 1 if printer error, 0 if O.K.

constants
	timeout_length = $02	%% 9/8 Printer Timeout ~2 x seconds length given

clauses
	% determine which media are attached to program
    determine_media(0):-  % test for printer attached
	%% PKC 9/8/90 Cut down Printer timeout to about 2-10 seconds
	%% Resetting all lpt1-4 as user may have redirected printer.
	membyte($0,$478,Out1),
	membyte($0,$478,timeout_length),
	membyte($0,$479,Out2),
	membyte($0,$479,timeout_length),
	membyte($0,$47a,Out3),
	membyte($0,$47a,timeout_length),
	membyte($0,$47b,Out4),
	membyte($0,$47b,timeout_length),

	%% PKC 21/8 change to Int. 21 - i.e. DOS, not BIOS, for printer test
	%% Fn. AH=$40, predefined handle (printer) $04
	%% CX=#bytes to write from address DS:DX (segment:offset data buffer)
	%% Send 1 character - carriage return - in string.
	%% Get address of string with prolog ptr_dword.

	TestString="\n",
	ptr_dword(TestString,DataSegment,DataOffset),
    	bios($21,reg($4000,$04,$01,DataOffset,0,0,DataSegment,0),_),

	%% Reset lpt1 timeout to previous value
	membyte($0,$478,Out1),
	membyte($0,$479,Out2),
	membyte($0,$47a,Out3),
	membyte($0,$47b,Out4),
	
	not(printer_error),!.
	%% printer_error asserted by critical error if printer not working.
    determine_media(1):-
	retractall(printer_error),!.

/* Here Endeth the Prolog code.
   You'll need to shove this in your code as appropriate.  The above stuff
   I have in 2 include files, plus a separate global predicate file.  You
   might need a goal as well :-}
   The menu function is a standard Turbo Prolog toolbox predicate, it just
   gives you a nice pop-up menu to choose an option.  Any other non-standard
   predicate I have above you can guess what it does from the name, if there
   are any more.
 */
------------------------------------------------------------------------
Final Word:  Even the above won't do you too much good on some networks, as
it is extremely difficult to know what the network will do.
For example, I'm using a Sun PC-NFS Network, which has the 'feature' of
not allowing you to send stuff to the printer from within a file using
the standard BIOS or DOS calls.  What actually happens is it checks the
printer DEVICE, which is NOT the printer.  Depending upon how I have my
configuration file set up, it will not send the character to the printer
until I quit the program, or 5 minutes after the command is issued.

I can't see any way around this problem.  By the way, to whoever it was who
told me about those addresses that indicated whether a printer was connected
or not, these only indicate the printer DEVICEs - this still won't tell you
whether the printer itself it turned on, out of paper, etc.  I think this
is what PC-NFS uses.

Disclaimer:  I take no responsibility for anything any of the above does, I
make no claims of anything, I only used trademarks to represent products & if
I done something wrong I didn't mean to.  Any statement to the affect that the
above is needed only because of the fact that the Intel processor is crippled
should not be construed as a statement which could be taken as libel.  Plus,
I wrote this in Parliament so this is under parliamentary privilege (so you
can't sue me), and I am probably the 13th cousin 42 times removed from some
diplomat or other, and hence am under diplomatic immunity.

Further Disclaimer:  I am actually a channel for a spirit of a DOS, C and
Prolog programmer, hence if you wish to complain about any of the above don't
tell me, complain to the spirit world.

Disclaimer to anything else:  None of the views above represent those of my
employers, unless it would make them money :-).


----------------------------------------------------------------------
Peter K. Campbell                      |
2/M6                                   | Oz Ph.:    03 541 6751
Telecom Research Laboratories          | Phone : + 613 541 6751
P.O. Box 249                           | Fax   : + 613 543 6026
Clayton 3168                           | Email : p.campbell@trl.OZ.AU
Victoria, Australia                    |
---------------------------------------------------------------------- 

ts@uwasa.fi (Timo Salmi LASK) (08/22/90)

In article <2102@trlluna.trl.oz> pcampb@shiva.trl.oz (Peter Campbell) writes:
>
>Thanks to all who sent answers re checking if printers are connected to
>a machine.  All answers unfortunately fell into the category of BIOS
>functions, but I finally managed to get a hold of the MS-DOS
>encyclopedia and managed to work out the way to do it in DOS.

The list misses one of the simplest and most effective of the
methods, that testing the IOResult of an empty write to printer in
Turbo Pascal.  If the printer default timeout is changed for the
duration of the test, the test can be made very fast.  The
facilities for this are included in /pc/ts/tspas22.arc available by
anonymous ftp from chyde.uwasa.fi. 

...................................................................
Prof. Timo Salmi        (Moderating at anon. ftp site 128.214.12.3)
School of Business Studies, University of Vaasa, SF-65101, Finland
Internet: ts@chyde.uwasa.fi Funet: gado::salmi Bitnet: salmi@finfun